/* Copyright (c) Microsoft Corporation.
   Licensed under the MIT License. */

/***************************************************************************

  movie.cpp

  Author: Sean Selitrennikoff

  Date: August, 1994

  This file contains all functionality for movie manipulation.

  THIS IS A CODE REVIEWED FILE

	Basic movie private classes:

		Movie Scene actions Undo Object (MUNS)

			BASE ---> UNDB ---> MUNB ---> MUNS


Note: The client of the movie engine should always do all actions through
MVIE level APIs, it should never use accessor functions to maniplate Scenes,
Actors, Text boxes, etc.

***************************************************************************/

#include "soc.h"
ASSERTNAME

#define krScaleMouseRecord BR_SCALAR(0.20)
#define krScaleMouseNonRecord BR_SCALAR(0.02)
#define kdwrMousePerSecond BR_SCALAR(100.0) // "mouse drag" speed for arrow keys

#define kdtsFrame (kdtsSecond / kfps)	// milliseconds per frame
#define kdtimFrame (kdtimSecond / kfps) // clock ticks per frame
#define kdtsCycleCels (kdtsFrame * 3) 	// delay when cycling cels
#define kdtsVlmFade 3					// number of seconds to fade
#define kdtimVlmFade (kdtimSecond/4)	// number of clock ticks necessary to split 1 sec into 4 events
#define kzrMouseScalingFactor BR_SCALAR(100.0)

const CHID kchidGstSource = 1;

//
// How many pixels from edge to warp cursor back to center
//
const long kdpInset = 50;

//
// Mouse scaling factor when rotating
//
const long krRotateScaleFactor = BR_SCALAR(0.001);

//
// Mouse scaling factor when sooner/latering.
//
const long krSoonerScaleFactor = BR_SCALAR(0.05);

//
// Number of ticks to pass before scrolling a single pixel
//
#define kdtsScrolling 5

//
//
// UNDO object for scene related actions:  Ins, New, and Rem
//
//
typedef class MUNS *PMUNS;

#define MUNS_PAR MUNB

enum MUNST
	{
    munstInsScen,
	munstRemScen,
	munstSetBkgd
	};

#define kclsMUNS 'MUNS'
class MUNS : public MUNS_PAR
	{
	RTCLASS_DEC
	MARKMEM
	ASSERT

protected:

	long _iscen;
	TAG _tag;
	PSCEN _pscen;
	MUNST _munst;
	MUNS(void) { }

public:

	static PMUNS PmunsNew(void);
	~MUNS(void);

	void SetIscen(long iscen) { _iscen = iscen; }
	void SetPscen(PSCEN pscen) { _pscen = pscen; _pscen->AddRef(); }
	void SetMunst(MUNST munst) { _munst = munst; }
	void SetTag(PTAG ptag) { _tag = *ptag; }

	virtual bool FDo(PDOCB pdocb);
	virtual bool FUndo(PDOCB pdocb);
	};



//
//
//
//  BEGIN MOVIE
//
//
//

RTCLASS(MVIE)
RTCLASS(MUNB)
RTCLASS(MUNS)


BEGIN_CMD_MAP(MVIE, CMH)
	ON_CID_ME(cidAlarm, &MVIE::FCmdAlarm, pvNil)
	ON_CID_ME(cidRender, &MVIE::FCmdRender, pvNil)
	ON_CID_GEN(cidSaveAndClose, pvNil, pvNil)
END_CMD_MAP_NIL()


//
// A file written by this version of movie.cpp receives this cvn.  Any
// file with this cvn value has exactly the same file format
//
const short kcvnCur = 2;

//
// A file written by this version of movie.cpp can be read by any version
// of movie.cpp whose kcvnCur is >= to this (this should be <= kcvnCur)
//
const short kcvnBack = 2;

//
// A file whose cvn is less than kcvnMin cannot be directly read by
// this version of movie.cpp (maybe a converter will read it).
// (this should be <= kcvnCur)
//
const short kcvnMin = 1;

//
// Movie file prefix
//
struct MFP
	{
	short bo;		// byte order
	short osk;		// which system wrote this
	DVER dver;		// chunky file version
	};
const BOM kbomMfp = 0x55000000;



//
// Used to keep track of the roll call list of the movie
//
struct MACTR
	{
	long arid;
	long cactRef;
	ulong grfbrws;  // browser properties
	TAG tagTmpl;
	};

typedef MACTR *PMACTR;

const BOM kbomMactr = (0xFC000000 | (kbomTag >> 4));


/****************************************************
 *
 * Constructor for movies.  This function is private, use PmvieNew()
 * for public construction.
 *
 ****************************************************/
MVIE::MVIE(void) : _clok(khidMvieClock)
{
	_aridLim = 0;
	_cno = cnoNil;
	_iscen = ivNil;
	_wit = witNil;
	_trans = transNil;
	_vlmOrg = 0;

	SetCundbMax(1);
}


/******************************************************************************
	_FSetPfilSave
		Given an FNI, looks for and remembers if found the FIL associated with
		it.  If the FIL was found, will also check to see if it's read-only.

	Returns:
		fFalse if the FIL wasn't found.
	
************************************************************ PETED ***********/
bool MVIE::_FSetPfilSave(PFNI pfni)
{
	AssertBaseThis(0);
	AssertPo(pfni, 0);

	long lAttrib;
	STN stnFile;

	/* Look for the file and remember FIL if found */
	ReleasePpo(&_pfilSave);
	_pfilSave = FIL::PfilFromFni(pfni);
	if (_pfilSave == pvNil)
		return fFalse;
	_pfilSave->AddRef();
	_fFniSaveValid = fTrue;

	/* Remember whether FIL is read-only; only relevant if we actually found
		the FIL, since if we didn't, we'll prompt for a new filename later
		anyway */
	pfni->GetStnPath(&stnFile);
#ifdef WIN
	_fReadOnly =
		(((lAttrib = GetFileAttributes(stnFile.Psz())) != 0xFFFFFFFF)
		&& (lAttrib & FILE_ATTRIBUTE_READONLY));
#else //MAC
	RawRtn();
#endif
	return fTrue;
}


/****************************************************
 *
 * Reads a movie from a file.
 *
 * Parameters:
 *  pmcc - Pointer to the movie client class block to use.
 *	pfni - File to read from.
 *	cno - CNO of the movie chunk, cnoNil if using the
 *		the first one in the file.
 *
 * Returns:
 *  pvNil if failure, else a pointer to the movie object.
 *
 ****************************************************/
PMVIE MVIE::PmvieNew(bool fHalfMode, PMCC pmcc, FNI *pfni, CNO cno)
{
	AssertNilOrPo(pfni, 0);
	AssertPo(pmcc, 0);

	bool fSuccess = fFalse, fBeganLongOp = fFalse;
	PMVIE pmvie;
	KID kid;
	CHID chid;
	TAGL *ptagl;
	PCFL pcfl = pvNil;
	BLCK blck;
	short bo;
	short osk;
	PGST pgstSource;

	//
	// Create the movie object
	//
	pmvie = NewObj MVIE;
	if (pmvie == pvNil)
		{
		goto LFail;
	    }

	//
	// Create the GL for holding undo events
	//
	pmvie->_pglpundb = GL::PglNew(size(PUNDB), 1);
	if (pmvie->_pglpundb == pvNil)
		{
		goto LFail;
	    }

    //
	// Create GL of actors in the movie
	//
	if (pvNil == pfni)
		{
		pmvie->_pgstmactr = GST::PgstNew(size(MACTR));
		if (pmvie->_pgstmactr == pvNil)
			{
			goto LFail;
		    }

		}

    //
	// Create the brender world
	//
	pmvie->_pbwld = BWLD::PbwldNew(pmcc->Dxp(), pmcc->Dyp(), fFalse, fHalfMode);
	if (pvNil == pmvie->_pbwld)
		{
		goto LFail;
		}

    //
	// Create the movie sound queue
	//
	pmvie->_pmsq = MSQ::PmsqNew();
	if (pvNil == pmvie->_pmsq)
		{
		goto LFail;
		}

	//
	// Save other variables
	//
	pmvie->_pmcc = pmcc;
	pmcc->AddRef();

	//
	// Do we initialize from a file?
	//
	if (pfni == pvNil)
		{
		return(pmvie);
		}

	/* Don't bother putting up the wait cursor unless we're reading a movie */
	vpappb->BeginLongOp();
	fBeganLongOp = fTrue;

	//
	// Get file to read from
	//
	pcfl = CFL::PcflOpen(pfni, fcflNil);
	if (pcfl == pvNil)
		{
		goto LFail;
		}

	if (!pmvie->FVerifyVersion(pcfl, &cno))
	  	goto LFail;
	pmvie->_cno = cno;

    //
	// Keep a reference to the user's file
	//
	if (!pmvie->_FSetPfilSave(pfni))
		{
		Bug("Hey, we just opened this file!");
		goto LFail;
		}

	//
	// Note (by *****): CRF *must* have 0 cache size, because of
	// serious cache-coherency problems otherwise.  TMPL data is not
	// read-only, and chunk numbers change over time.
	//
	pmvie->_pcrfAutoSave = CRF::PcrfNew(pcfl, 0);  // cache size must be 0
	if (pvNil == pmvie->_pcrfAutoSave)
		{
		goto LFail;
		}

	//
	// Merge this document's source title list
	//
	if (pcfl->FGetKidChidCtg(kctgMvie, cno, kchidGstSource, kctgGst,
		&kid) && pcfl->FFind(kid.cki.ctg, kid.cki.cno, &blck))
		{
		pgstSource = GST::PgstRead(&blck, &bo, &osk);
		if (pvNil != pgstSource)
			{
			// Ignore result...we can survive failure
			vptagm->FMergeGstSource(pgstSource, bo, osk);
			ReleasePpo(&pgstSource);
			}
		}

    //
	// Get the movie roll-call
	//
	Assert(pcfl == pmvie->_pcrfAutoSave->Pcfl(), "pcfl isn't right");
	if (!FReadRollCall(pmvie->_pcrfAutoSave, cno, &pmvie->_pgstmactr, &pmvie->_aridLim))
		{
		pmvie->_pgstmactr = pvNil;
		goto LFail;
		}

	//
	// Get all the content tags
	//
	ptagl = pmvie->_PtaglFetch();
	if (ptagl == pvNil)
		{
		goto LFail;
		}

    //
	// Now bring all the tags into cache
	//
	if (!ptagl->FCacheTags())
		{
		ReleasePpo(&ptagl);
		goto LFail;
		}

	ReleasePpo(&ptagl);

	//
	// Set the movie title
	//
	pmvie->_SetTitle(pfni);

	//
	// Count the number of scenes in the movie
	//
	for (chid = 0;
		 pcfl->FGetKidChidCtg(kctgMvie, cno, chid, kctgScen, &kid);
		 chid++, pmvie->_cscen++
		)
		{
		}

	fSuccess = fTrue;

LFail:
	ReleasePpo(&pcfl);
	if (!fSuccess)
		ReleasePpo(&pmvie);
	if (fBeganLongOp)
		vpappb->EndLongOp();

	return(pmvie);
}


/******************************************************************************
	FReadRollCall
		Reads the roll call off file for a given movie.  Will swapbytes the
		extra data in the GST if necessary, and will report back on the
		highest arid found.
	
	Arguments:
		PCFL pcfl       -- the file the movie is on
		PCRF pcrf       -- the autosave CRF for the movie's ACTR tags
		CNO cno         -- the cno of the movie
		PGST *ppgst     -- the PGST to fill in
		long *paridLim  -- the max arid to update
	
	Returns: fTrue if there were no failures, fFalse otherwise
	
************************************************************ PETED ***********/
bool MVIE::FReadRollCall(PCRF pcrf, CNO cno, PGST *ppgst, long *paridLim)
{
	AssertPo(pcrf, 0);
	AssertVarMem(ppgst);
	Assert(*ppgst == pvNil, "Overwriting existing GST");
	AssertNilOrVarMem(paridLim);

	short bo;
	long imactr, imactrMac;
	PCFL pcfl = pcrf->Pcfl();
	KID kid;
	BLCK blck;
	MACTR mactr;

	if (!pcfl->FGetKidChidCtg(kctgMvie, cno, 0, kctgGst, &kid) ||
		!pcfl->FFind(kid.cki.ctg, kid.cki.cno, &blck))
		{
        PushErc(ercSocBadFile);
		goto LFail;
		}

	*ppgst = GST::PgstRead(&blck, &bo);
	if (*ppgst == pvNil)
		goto LFail;

	imactrMac = (*ppgst)->IvMac();
	for (imactr = 0; imactr < imactrMac; imactr++)
		{
		(*ppgst)->GetExtra(imactr, &mactr);
		if (bo == kboOther)
			SwapBytesBom(&mactr, kbomMactr);

		if (paridLim != pvNil && mactr.arid >= *paridLim)
			*paridLim = mactr.arid + 1;

		// Open the tags, since they might be TDTs
		AssertDo(vptagm->FOpenTag(&mactr.tagTmpl, pcrf),
			"Should never fail when not copying the tag");

		(*ppgst)->PutExtra(imactr, &mactr);
		}

	return fTrue;
LFail:
	TrashVar(ppgst);
	return fFalse;
}


/******************************************************************************
	Flush
		Ensures that the data has been written to disk.
	
************************************************************ PETED ***********/
void MVIE::Flush(void)
{
	if (_fFniSaveValid)
		{
		AssertPo(_pfilSave, 0);
		_pfilSave->Flush();
		}
}


/****************************************************
 *
 * Nuke unused sounds from the file
 * Msnd chunks exist as children of the movie chunk.
 * They are also children of any scene which uses
 * them.
 * Note: The ref count does not reflect how many
 * scene or actor events reference the sound.
 *
 * Parms:
 *    bool fPurgeAll -- if fFalse, only purge invalid sounds
 *
 ****************************************************/
void MVIE::_DoSndGarbageCollection(bool fPurgeAll)
{
	AssertThis(0);

	// Before releasing, get rid of all msnd chunks with
	// cactref == 1 (the refcnt from being a child of the
	// movie chunk)

	long ikid;

	if (pvNil == _pcrfAutoSave)
		return;

	PCFL pcfl = _pcrfAutoSave->Pcfl();
	if (pvNil == pcfl)
		return;

	/* Go backwards, since we might delete some */
	ikid = pcfl->Ckid(kctgMvie, _cno);
	while (ikid--)
		{
		KID kid;
		KID kidT;

		if (!pcfl->FGetKid(kctgMvie, _cno, ikid, &kid))
			{
			Bug("CFL returned bogus Ckid()");
			break;
			}

		if (kid.cki.ctg != kctgMsnd)
			continue;

		if (pcfl->CckiRef(kctgMsnd, kid.cki.cno) > 1)
			continue;

		/* Always purge MSNDs with no actual sound attached */
		if (fPurgeAll || !pcfl->FGetKidChid(kctgMsnd, kid.cki.cno,
				kchidSnd, &kidT))
			{
			pcfl->DeleteChild(kctgMvie, _cno, kctgMsnd, kid.cki.cno, kid.chid);
			}
		}

	return;
}

/****************************************************
 *
 * Destructor for movies.
 *
 ****************************************************/
MVIE::~MVIE(void)
{
	AssertBaseThis(0);

	long imactr;
	MACTR mactr;

	ReleasePpo(&_pcrfAutoSave);
	ReleasePpo(&_pfilSave);

	if (FPlaying())
		{
		AssertPo(Pmcc(), 0);
		Pmcc()->EnableAccel();
		Pmcc()->PlayStopped();
		vpsndm->StopAll();

		// if we were fading, then restore sound volume
		if (_vlmOrg)
			{
			vpsndm->SetVlm(_vlmOrg);
			_vlmOrg = 0;
			}

		}

	if (Pmcc() != pvNil)
		{
		Pmcc()->UpdateRollCall();
		}

	if (Pscen() != pvNil)
		{

    	//
    	// Release open scene.
    	//
		SCEN::Close(&_pscenOpen);
		}

    //
	// Release the roll call
	//
	if (_pgstmactr != pvNil)
		{
		for (imactr = 0; imactr < _pgstmactr->IvMac(); imactr++)
			{
			_pgstmactr->GetExtra(imactr, &mactr);
			vptagm->CloseTag(&mactr.tagTmpl);
			}
		ReleasePpo(&_pgstmactr);
		}

	//
	// Release the call back class
	//
	ReleasePpo(&_pmcc);

	//
	// Release the brender world
	//
	ReleasePpo(&_pbwld);

	//
	// Release the sound queue
	//
	ReleasePpo(&_pmsq);

	ReleasePpo(&_pglclrThumbPalette);

    return;
}


#ifdef DEBUG

/****************************************************
 * Mark memory used by the MVIE
 *
 * Parameters:
 * 	None.
 *
 * Returns:
 *  None.
 *
 ****************************************************/
void MVIE::MarkMem(void)
{
	AssertThis(0);

	MVIE_PAR::MarkMem();

	MarkMemObj(_pcrfAutoSave);

	MarkMemObj(_pfilSave);

	MarkMemObj(_pgstmactr);

	MarkMemObj(Pscen());

	MarkMemObj(Pbwld());

	MarkMemObj(_pmcc);

	MarkMemObj(_pmsq);

	MarkMemObj(_pglclrThumbPalette);
}

/***************************************************************************
 *
 * Assert the validity of the MVIE.
 *
 * Parameters:
 *	grf - Bit field of options
 *
 * Returns:
 *  None.
 *
 **************************************************************************/
void MVIE::AssertValid(ulong grf)
{
	MVIE_PAR::AssertValid(fobjAllocated);

	AssertNilOrPo(_pcrfAutoSave, 0);
	AssertPo(_pgstmactr, 0);
	AssertNilOrPo(Pbwld(), 0);
	AssertPo(&_clok, 0);
	AssertPo(_pmcc, 0);
}

#endif //DEBUG


/***************************************************************************
 *
 * Returns a list of all tags being used by this MVIE
 *
 * Parameters:
 *	None.
 *
 * Returns:
 *  A TAGL (list of tags that the movie uses)
 *
 **************************************************************************/
PTAGL MVIE::_PtaglFetch(void)
{
	AssertThis(0);
	Assert(_pcrfAutoSave != pvNil, "need pcrfAutosave");

	PTAGL ptagl;
	KID kid;
	CHID chid;

	ptagl = TAGL::PtaglNew();
	if (pvNil == ptagl)
		return pvNil;

	//
	// Add each scene's tags
	//
	for (chid = 0;
		 _pcrfAutoSave->Pcfl()->FGetKidChidCtg(kctgMvie, _cno, chid, kctgScen, &kid);
		 chid++)
		{
		if (!SCEN::FAddTagsToTagl(_pcrfAutoSave->Pcfl(), kid.cki.cno, ptagl))
			{
			ReleasePpo(&ptagl);
			return pvNil;
			}
		}

	return ptagl;
}


/****************************************************
 *
 * Fetches the iarid'th actor in the movie.
 *
 * Parameters:
 *   iarid - The arid index to fetch.
 *   parid - Pointer to storage for the found arid.
 *	 pstn  - Pointer to the actors name.
 *	 pcactRef - Number of scenes the actor is in.
 *	 *ptagTmpl - Template tag
 * Returns:
 *	 fTrue if successful, else fFalse if out of range.
 *	
 ****************************************************/
bool MVIE::FGetArid(long iarid, long *parid, PSTN pstn, long *pcactRef,
	PTAG ptagTmpl)
{
	AssertThis(0);
	AssertPvCb(parid, size(long));
	AssertVarMem(pcactRef);

	MACTR mactr;
	
	if (iarid < 0 || iarid >= _pgstmactr->IvMac())
		{
		return fFalse;
		}

	_pgstmactr->GetStn(iarid, pstn);
	_pgstmactr->GetExtra(iarid, &mactr);
	*parid = mactr.arid;
	*pcactRef = mactr.cactRef;
	if (pvNil != ptagTmpl)
		*ptagTmpl = mactr.tagTmpl;
	return fTrue;
}


/****************************************************
 *
 * User chose arid in the roll call.  If actor
 * exists in this scene and is onstage, select it.
 * Else if actor is offstage, bring it onstage.
 * If actor is not in this scene, create and add it.
 *
 * Parameters:
 *	arid - The arid to search for, or create.
 *
 * Returns:
 *  fTrue if successful, else fFalse.
 *
 ****************************************************/
bool MVIE::FChooseArid(long arid)
{
	AssertThis(0);
	AssertPo(Pscen(), 0);

	PACTR pactr, pactrDup;
	MACTR mactr;
	long imactr;
	PMVU pmvu;

	pmvu = (PMVU)PddgGet(0);
	if (pmvu == pvNil)
 		{
		return(fFalse);
		}
	AssertPo(pmvu, 0);

	pactr = Pscen()->PactrFromArid(arid);

	if (pvNil != pactr)
		{

		if (!pactr->FIsInView())
			{

			if (!pactr->FDup(&pactrDup, fTrue))
				{
				return(fFalse);
				}
			AssertPo(pactrDup, 0);

			if (!pactr->FAddOnStageCore())
				{
                ReleasePpo(&pactrDup);
				return fFalse;
				}
			Pscen()->SelectActr(pactr);
			AssertDo(FAddToRollCall(pactr, pvNil), "Should never fail");
			pmvu->StartPlaceActor();
            pmvu->SetActrUndo(pactrDup);

			}

		Pscen()->SelectActr(pactr);
		InvalViews();
		return(fTrue);
		}
	else
		{

		//
		// Search roll call for actor, and create
		// a new actor for it.
		//
		for (imactr = 0; imactr < _pgstmactr->IvMac(); imactr++)
			{
	        _pgstmactr->GetExtra(imactr, &mactr);
			if (mactr.arid == arid)
				{
				pactr = ACTR::PactrNew(&(mactr.tagTmpl));

				if (pactr == pvNil)
					{
					return(fFalse);
					}

				AssertPo(pactr, 0);

				pactr->SetArid(arid);
				
					
				if (!Pscen()->FAddActr(pactr))
					{
					ReleasePpo(&pactr);
					return(fFalse);
					}

				pmvu->StartPlaceActor();
				ReleasePpo(&pactr);
				return(fTrue);
				}

			}

		}

	return(fFalse);
}


/****************************************************
 *
 * Return the arid of the selected actor.
 *
 * Parameters:
 *   None.
 *
 * Returns:
 *   Arid of the selected actor, else aridNil.
 *
 ****************************************************/
long MVIE::AridSelected(void)
{
	AssertThis(0);

	if ((pvNil != Pscen()) && (pvNil != Pscen()->PactrSelected()))
		{
		return Pscen()->PactrSelected()->Arid();
		}
	else
		{
		return aridNil;
		}
}

/****************************************************
 *
 * Fetches the name of the actor with arid.
 *
 * Parameters:
 *   arid - The arid to fetch.
 *   pstn - Pointer to storage for the found name.
 *
 * Returns:
 *	 fTrue if successful, else fFalse if failure.
 *	
 ****************************************************/
bool MVIE::FGetName(long arid, PSTN pstn)
{
	AssertThis(0);
	AssertPo(pstn, 0);

	MACTR mactr;
	long imactr;

	for (imactr = 0; imactr < _pgstmactr->IvMac(); imactr++)
		{
		_pgstmactr->GetExtra(imactr, &mactr);
		if (mactr.arid == arid)
			{
			_pgstmactr->GetStn(imactr, pstn);
			return(fTrue);
			}
		}	

	return(fFalse);
}



/****************************************************
 *
 * Sets the name of the actor with arid.
 *
 * Parameters:
 *   arid - The arid to set.
 *   pstn - Pointer to the name.
 *
 * Returns:
 *	 fTrue if successful, else fFalse.
 *	
 ****************************************************/
bool MVIE::FNameActr(long arid, PSTN pstn)
{
	AssertThis(0);
	AssertIn(arid, 0, 500);
	AssertPo(pstn, 0);

	MACTR mactr;
	long imactr;

	for (imactr = 0; imactr < _pgstmactr->IvMac(); imactr++)
		{

		_pgstmactr->GetExtra(imactr, &mactr);
		if (mactr.arid == arid)
			{

			if(_pgstmactr->FPutStn(imactr, pstn))
				{

				_pmcc->UpdateRollCall();	
				return(fTrue);

				}

			return(fFalse);

			}

		}	

	return(fFalse);
}

/****************************************************
 *
 * Identifies whether the mactr is in prop browser
 *
 * Parameters:
 *   imactr - index in _pgstmactr
 *
 * Returns:
 *	 bool
 *	
 ****************************************************/
bool MVIE::FIsPropBrwsIarid(long iarid)
{
	AssertThis(0);
	AssertIn(iarid, 0, _pgstmactr->IvMac());

	MACTR mactr;
	_pgstmactr->GetExtra(iarid, &mactr);
	return FPure(mactr.grfbrws & fbrwsProp);
}

/****************************************************
 *
 * Identifies whether the mactr is in 3dtext object
 *
 * Parameters:
 *   imactr - index in _pgstmactr
 *
 * Returns:
 *	 bool
 *	
 ****************************************************/
bool MVIE::FIsIaridTdt(long iarid)
{
	AssertThis(0);
	AssertIn(iarid, 0, _pgstmactr->IvMac());

	MACTR mactr;
	_pgstmactr->GetExtra(iarid, &mactr);
	return FPure(mactr.grfbrws & fbrwsTdt);
}


/****************************************************
 *
 * Sets the tag of the actor with arid.
 *
 * Parameters:
 *   arid - The arid to set.
 *   ptag - Pointer to the TMPL tag.
 *
 * Returns:
 *	 none
 *	
 ****************************************************/
void MVIE::ChangeActrTag(long arid, PTAG ptag)
{
	AssertThis(0);
	AssertIn(arid, 0, 500);
	AssertVarMem(ptag);

	MACTR mactr;
	long imactr;

	for (imactr = 0; imactr < _pgstmactr->IvMac(); imactr++)
		{

		_pgstmactr->GetExtra(imactr, &mactr);
		if (mactr.arid == arid)
			{

			mactr.tagTmpl = *ptag;
			_pgstmactr->PutExtra(imactr, &mactr);
			return;

			}

		}	

	Bug("no such actor");
}



/****************************************************
 *
 * This adds an actor to the roll call, if not already there, or
 * adds a reference count if it already exists.
 *
 * Parameters:
 *   Pointer to the actor to add.
 *
 * Returns:
 *   fTrue if successful, else fFalse indicating out of resources.
 *
 ****************************************************/
bool MVIE::FAddToRollCall(ACTR *pactr, PSTN pstn)
{
	AssertThis(0);
	AssertPo(pactr, 0);
	AssertNilOrPo(pstn, 0);  // can be pvNil if the actor is already in the movie.

	MACTR mactr;
	long imactr;

	if (pactr->Arid() != aridNil)
		{
    	//
    	// Search for an actor with the same arid
    	//
    	for (imactr = 0; imactr < _pgstmactr->IvMac(); imactr++)
    		{

            _pgstmactr->GetExtra(imactr, &mactr);
    		if (mactr.arid == pactr->Arid())
    			{
				TAG tagTmpl;
				mactr.cactRef++;
				// TDTs sometimes need to update tagTmpl
				pactr->GetTagTmpl(&tagTmpl);
				if (fcmpEq != TAGM::FcmpCompareTags(&mactr.tagTmpl, &tagTmpl))
					{
					TAGM::CloseTag(&mactr.tagTmpl);
					mactr.tagTmpl = tagTmpl;
 					// ACTR::GetTagTmpl doesn't AddRef the pcrf, so do it here:
					TAGM::DupTag(&tagTmpl);
					}
				_pgstmactr->PutExtra(imactr, &mactr);
				Pmcc()->UpdateRollCall();
    			return(fTrue);
    			}

    		}

		}
	else
		{
		pactr->SetArid(_aridLim++);
		}

	AssertPo(pstn, 0);

    //
	// This is a new actor, add it to the roll call
	//
    mactr.arid = pactr->Arid();
	mactr.grfbrws = fbrwsNil;
	if (pactr->FIsPropBrws())
		{
		mactr.grfbrws |= fbrwsProp;
		if (pactr->FIsTdt())
			{
			mactr.grfbrws |= fbrwsTdt;
			}
		}
	mactr.cactRef = 1;
	pactr->GetTagTmpl(&mactr.tagTmpl);
	// Open the tag, since it might be a TDT
	AssertDo(vptagm->FOpenTag(&mactr.tagTmpl, _pcrfAutoSave),
		"Should never fail when not copying the tag");
	if (_pgstmactr->FAddStn(pstn, &mactr))
		{
		Pmcc()->UpdateRollCall();
		return(fTrue);
		}

	return(fFalse);
}

/****************************************************
 *
 * This removes an actor from the roll call, if the reference count
 * drops to zero, else it decrements the reference count.
 *
 * Parameters:
 *   Pointer to the actor to remove.
 *
 * Returns:
 *   None.
 *
 ****************************************************/
void MVIE::RemFromRollCall(ACTR *pactr, bool fDelIfOnlyRef)
{
	AssertThis(0);
	AssertPo(pactr, 0);

	MACTR mactr;
	long imactr;

	//
	// Search for the actor in the roll call
	//
	for (imactr = 0; imactr < _pgstmactr->IvMac(); imactr++)
		{
		_pgstmactr->GetExtra(imactr, &mactr);
		if (mactr.arid == pactr->Arid())
			{
            mactr.cactRef--;
			Assert(mactr.cactRef >= 0, "Too many removes");
			if (fDelIfOnlyRef && mactr.cactRef == 0)
				{
				vptagm->CloseTag(&mactr.tagTmpl);
				_pgstmactr->Delete(imactr);
				}
			else
				{
				_pgstmactr->PutExtra(imactr, &mactr);
				}

			if (mactr.cactRef == 0)
				{
				Pmcc()->UpdateRollCall();
				}
			return;

			}
		}

    Bug("Tried to remove an invalid actor");
}

/****************************************************
 *
 * Changes the scene in the movie currently being referenced.
 *
 * Parameters:
 *	iscen - Scene number to go to.
 *
 * Returns:
 *
 *  fTrue, if successful, else fFalse, indicating the
 *  same scene is still open (if possible).
 *
 ****************************************************/
bool MVIE::FSwitchScen(long iscen)
{
	AssertThis(0);
	Assert(iscen == ivNil || FIn(iscen, 0, Cscen()), "iscen out of range");
	Assert((iscen == ivNil) || (_pcrfAutoSave != pvNil), "Invalid save file");

	PSCEN pscen;
	KID kid;
	long iscenOld;
	bool fRet = fTrue;

	if (iscen == _iscen)
		{
		return(fTrue);
		}
		
	//
	// Close the current scene.
	//
	iscenOld = _iscen;
	if (!_FCloseCurrentScene())
		{
		return(fFalse);
		}

	//
	// Stop all looping non-midi sounds
	//
	vpsndm->StopAll(sqnNil, sclLoopWav);

	//
	// If memory is low, release unreferenced content BACOs to reduce thrashing.
	// Note that APP::MemStat() is unavailable from the movie engine
	//
#ifdef WIN
	MEMORYSTATUS ms;
	ms.dwLength = size(MEMORYSTATUS);
	GlobalMemoryStatus(&ms);
	if (ms.dwMemoryLoad == 100)
		{
		// No physical RAM to spare, so free some stuff
		vptagm->ClearCache(sidNil, ftagmMemory);
		}

#endif //WIN

	if (iscen == ivNil)
		{
		Assert(_pscenOpen == pvNil, "_FCloseCurrentScene didn't clear this");
		Assert(_iscen == ivNil, "_FCloseCurrentScene didn't clear this");
		return(fTrue);
		}

LRetry:

    //
	// Get info for next scene and read it in.
	//
	AssertDo(_pcrfAutoSave->Pcfl()->FGetKidChidCtg(kctgMvie, _cno, iscen, kctgScen, &kid),
			 "Should never fail");

	pscen = SCEN::PscenRead(this, _pcrfAutoSave, kid.cki.cno);

	if ((pscen == pvNil) || !pscen->FPlayStartEvents())
		{
		_pscenOpen = pvNil;
		_iscen = ivNil;

		if (pscen != pvNil)
			{
			SCEN::Close(&pscen);
			}

		if (iscenOld != ivNil)
			{
			iscen = iscenOld;
			iscenOld = ivNil;
			fRet = fFalse;
			goto LRetry;
			}

		_pmcc->SceneChange();
		return(fFalse);
		}

	_pscenOpen = pscen;
	_pmcc->SceneChange();
	_iscen = iscen;

	if (!pscen->FGotoFrm(pscen->NfrmFirst()))
		{

		_pscenOpen = pvNil;
		_iscen = ivNil;
		SCEN::Close(&pscen);

		if (iscenOld != ivNil)
			{
			iscen = iscenOld;
			iscenOld = ivNil;
			fRet = fFalse;
			goto LRetry;
			}

		return(fFalse);
		}

	pscen->UpdateSndFrame();
	InvalViewsAndScb();

	return(fRet);
}

/****************************************************
 *
 * Creates a new scene and inserts it as scene number iscen.
 *
 * Parameters:
 *	iscen - Scene number to insert the new scene as.
 *
 * Returns:
 *  fTrue, if successful, else fFalse.
 *
 ****************************************************/
bool MVIE::FNewScenInsCore(long iscen)
{
	AssertThis(0);
	AssertIn(iscen, 0, Cscen() + 1);

	PSCEN pscen;

	//
	// Create the new scene.
	//
	pscen = SCEN::PscenNew(this);
	if (pscen == pvNil)
		{
		return(fFalse);
		}

	if (!FInsScenCore(iscen, pscen))
		{
		SCEN::Close(&pscen);
		return(fFalse);
		}

	SCEN::Close(&pscen);
	return(fTrue);
}

/****************************************************
 *
 * Moves up/down all the chids by one.
 *
 * Parameters:
 *	chid - chid number to start at.
 *	fDown- fTrue if move down, else move up.
 *
 * Returns:
 *  None.
 *
 ****************************************************/
void MVIE::_MoveChids(CHID chid, bool fDown)
{
	AssertThis(0);

	PCFL pcfl = _pcrfAutoSave->Pcfl();
	KID kid;
	CHID chidTmp;

	if (fDown)
		{
    	//
    	// Move down chids of all old scenes (increase by one)
    	//
    	for (chidTmp = _cscen; chidTmp > chid;)
    		{
            chidTmp--;
    		AssertDo(pcfl->FGetKidChidCtg(kctgMvie, _cno, chidTmp, kctgScen, &kid),
    				 "Should never fail");

    		pcfl->ChangeChid(kctgMvie, _cno, kctgScen, kid.cki.cno, chidTmp, chidTmp + 1);
    		}
		}
	else
		{
    	//
    	// Move up chids of all old scenes (decrease by one)
    	//
    	for (chidTmp = chid; chidTmp < (CHID)_cscen; chidTmp++)
    		{
    		AssertDo(pcfl->FGetKidChidCtg(kctgMvie, _cno, chidTmp + 1, kctgScen, &kid),
    				 "Should never fail");

    		pcfl->ChangeChid(kctgMvie, _cno, kctgScen, kid.cki.cno, chidTmp + 1, chidTmp);
    		}

		}
}


/******************************************************************************
	_FIsChild
		Enumerates the children of the MVIE chunk and reports whether the
		given (ctg, cno) chunk is an actual child of the MVIE chunk.
	
	Arguments:
		PCFL pcfl  --  the file on which to check
		CTG ctg    --  these are self-explanatory
		CNO cno
	
	Returns:  fTrue if the (ctg, cno) chunk is an immediate child of the MVIE
	
************************************************************ PETED ***********/
bool MVIE::_FIsChild(PCFL pcfl, CTG ctg, CNO cno)
{
	bool fIsChild = fFalse;
	long ckid, ikid;
	KID kid;

	ckid = pcfl->Ckid(kctgMvie, _cno);
	for (ikid = 0; ikid < ckid; ikid++)
		{
		if (!pcfl->FGetKid(kctgMvie, _cno, ikid, &kid))
			{
			Bug("CFL returned bogus ckid");
			break;
			}

		if (kid.cki.ctg == ctg && kid.cki.cno == cno)
			{
			fIsChild = fTrue;
			break;
			}
		}

	return fIsChild;
}


/****************************************************
 *
 * Adopt the scene user sounds as children of the movie.
 * Msnds are children of the movie unless they are
 * unused and deleted when the movie closes.
 *
 * Parameters
 * pcfl
 * cnoScen
 *
 *	Returns:
 * success or failure
 *
 ****************************************************/
bool MVIE::_FAdoptMsndInMvie(PCFL pcfl, CNO cnoScen)
{
	AssertThis(0);
	AssertPo(pcfl, 0);

	CHID chidMvie;
	long ckid, ikid;
	KID kid;

	ckid = pcfl->Ckid(kctgScen, cnoScen);
	for (ikid = 0; ikid < ckid; ikid++)
		{
		if (!pcfl->FGetKid(kctgScen, cnoScen, ikid, &kid))
			{
			Bug("CFL returned bogus ckid");
			break;
			}

		if (kid.cki.ctg == kctgMsnd)
			{
			if (!_FIsChild(pcfl, kctgMsnd, kid.cki.cno))
				{
				// Adopt as a child of the movie
				chidMvie = _ChidMvieNewSnd();
				if (!pcfl->FAdoptChild(kctgMvie, _cno, kctgMsnd, kid.cki.cno,
						chidMvie))
					{
					goto LFail;
					}
				}
			}
		}

	return fTrue;
LFail:
	return fFalse;
}


/****************************************************
 *
 * Resolves a sound tag & chid to be a current tag
 *
 * Note: Msnds are children of the current scene.
 * Due to sound import, the cno can change.
 *
 * Parameters
 * ptag (with *ptag.cno possibly out of date)
 * chid (valid only for user snd)
 * cnoScen (if cnoNil, use current scene's cno
 *
 *	Returns:
 * Updated *ptag
 *
 ****************************************************/
bool MVIE::FResolveSndTag(PTAG ptag, CHID chid, CNO cnoScen, PCRF pcrf)
{
	AssertThis(0);
	AssertVarMem(ptag);
	AssertNilOrVarMem(pcrf);

	KID kidScen;
	KID kid;
	TAG tagNew = *ptag;
	PCFL pcfl;

	if (pvNil == pcrf)
		pcrf = _pcrfAutoSave;
	pcfl = pcrf->Pcfl();

	if (ptag->sid != ksidUseCrf)
		return fTrue;
	if (cnoNil == cnoScen)
	   	{
		if (!pcfl->FGetKidChidCtg(kctgMvie, _cno, _iscen, kctgScen, &kidScen))
			return fFalse;
		cnoScen = kidScen.cki.cno;
		}

	if (!pcfl->FGetKidChidCtg(kctgScen, cnoScen, chid, kctgMsnd, &kid))
			return fFalse;

	if (ptag->cno != kid.cki.cno)
		{
		// As the pcrf has not changed, it is not essential
		// to close & open the respective tags.
		tagNew.cno = kid.cki.cno;
		if (!TAGM::FOpenTag(&tagNew, pcrf))
			return fFalse;
		TAGM::CloseTag(ptag);
		*ptag = tagNew;
		}
	return fTrue;
}

/****************************************************
 *
 * Finds the chid (child of the current scene) for a
 * given sound tag.  Adopt it if not found.
 * Meaningful for user sounds only
 *
 * Parameters
 * cno
 *
 *	Returns:
 * Updated *pchid
 *
 ****************************************************/
bool MVIE::FChidFromUserSndCno(CNO cno, CHID *pchid)
{
	AssertThis(0);
	AssertVarMem(pchid);

	KID kidScen;
	KID kid;
	long ckid;
	long ikid;
	PCFL pcfl = _pcrfAutoSave->Pcfl();

	if (!pcfl->FGetKidChidCtg(kctgMvie, _cno, _iscen, kctgScen, &kidScen))
		return fFalse;
	ckid = pcfl->Ckid(kctgScen, kidScen.cki.cno);
	for (ikid = 0; ikid < ckid; ikid++)
		{
		if (!pcfl->FGetKid(kctgScen, kidScen.cki.cno, ikid, &kid))
			return fFalse;
		if (kid.cki.ctg != kctgMsnd)
			continue;
		if (kid.cki.cno != cno)
			continue;
		*pchid = kid.chid;
		return fTrue;
		}

	*pchid = _ChidScenNewSnd();
	if (!pcfl->FAdoptChild(kctgScen, kidScen.cki.cno, kctgMsnd, cno, *pchid))
		return fFalse;
	return fTrue;
}


/****************************************************
 *
 * Copies a sound file to the movie. (Importing snd)
 * Sounds are written as MSND children of the current
 * scene chunk
 *	 	
 * Parameters:
 *	pfilSrc, sty
 *
 * Returns:
 *  *pcno = cno of chunk written
 *  fFalse if there was a failure, else fTrue.
 *
 ****************************************************/
bool MVIE::FCopySndFileToMvie(PFIL pfilSrc, long sty, CNO *pcno, PSTN pstn)
{
	AssertThis(0);
	AssertVarMem(pfilSrc);
	AssertVarMem(pcno);
	AssertNilOrPo(pstn, 0);
	Assert(_pcrfAutoSave != pvNil, "Bad working file.");

	PCFL pcfl;
	FNI fniSrc;
	CHID chid;
	KID kidScen;

	pcfl = _pcrfAutoSave->Pcfl();
	
	//
	// Ensure we will be writing to the temp file
	//
	if (!_FUseTempFile())
		{
		return fFalse;
		}

	pfilSrc->GetFni(&fniSrc);
	if (fniSrc.Ftg() == kftgMidi)
		{
		if (!MSND::FCopyMidi(pfilSrc, pcfl, pcno, pstn))
			goto LFail;
		}
	else
		{
		if (!MSND::FCopyWave(pfilSrc, pcfl, sty, pcno, pstn))
			goto LFail;
		}

	AssertDo(pcfl->FGetKidChidCtg(kctgMvie, _cno, _iscen, kctgScen, &kidScen),
		"Scene chunk doesn't exist!");

	chid = _ChidMvieNewSnd();
	if (!pcfl->FAdoptChild(kctgMvie, _cno, kctgMsnd, *pcno, chid))
		{
		pcfl->Delete(kctgMsnd, *pcno);
		return fFalse;
		}
	if (!pcfl->FSave(kctgSoc))
		return fFalse;

	_fGCSndsOnClose = fTrue;

	return fTrue;
LFail:
	if (!vpers->FIn(ercSocBadSoundFile))
		PushErc(ercSocCantCopyMsnd);
	return fFalse;
}


/****************************************************
 *
 * Copy Msnd chunk from specified movie *pcfl to
 * current movie
 * Parameters:
 *	pcfl : Source file
 *
 * Returns:
 *  fFalse if there was a failure, else fTrue.
 *	*pcnoDest
 *
 ****************************************************/
bool MVIE::FCopyMsndFromPcfl(PCFL pcflSrc, CNO cnoSrc, CNO *pcnoDest)
{
	AssertBaseThis(0);
	AssertPo(pcflSrc, 0);
	AssertVarMem(pcnoDest);

	PCFL pcflDest;
	KID kidScen;
	CHID chid;
	FNI fni;

	if (!FEnsureAutosave())
		return fFalse;		
	pcflDest = _pcrfAutoSave->Pcfl();
	pcflSrc->GetFni(&fni);

	// Copy the msnd chunk from one movie to another
	// Wave or Midi
	if (!pcflSrc->FCopy(kctgMsnd, cnoSrc, pcflDest, pcnoDest))
		return fFalse;

	AssertDo(pcflDest->FGetKidChidCtg(kctgMvie, _cno, _iscen, kctgScen, &kidScen),
		"Scene chunk doesn't exist!");

	chid = _ChidMvieNewSnd(); 		// Find a unique chid for the new sound
	if (!pcflDest->FAdoptChild(kctgMvie, _cno, kctgMsnd, *pcnoDest, chid))
		{
		pcflDest->Delete(kctgMsnd, *pcnoDest);
		return fFalse;
		}
	if (!pcflDest->FSave(kctgSoc))
		return fFalse;
	return fTrue;
}


/****************************************************
 *
 * Choose Scene Chid for New Sound
 * Note: _pcrfAutoSave is expected to be current
 * Parameters:
 *	none
 *
 * Returns:
 *  unique chid for new msnd chunk child of scene
 *
 ****************************************************/
CHID MVIE::_ChidScenNewSnd(void)
{
	AssertBaseThis(0);
	PCFL pcfl = _pcrfAutoSave->Pcfl();
	long ckid;
	long chid;
	KID kidScen;
	KID kid;

	if (!pcfl->FGetKidChidCtg(kctgMvie, _cno, _iscen, kctgScen, &kidScen))
		return fFalse;

	ckid = pcfl->Ckid(kctgScen, kidScen.cki.cno);
	for (chid = 0; chid < ckid; chid++)
		{
		if (!pcfl->FGetKidChidCtg(kctgScen, kidScen.cki.cno, chid, kctgMsnd, &kid))
			return (CHID)chid;
		}
	return (CHID)chid;
}

/****************************************************
 *
 * Choose Mvie Chid for New Sound
 * Note: _pcrfAutoSave is expected to be current
 * Parameters:
 *	none
 *
 * Returns:
 *  unique chid for new msnd chunk child of scene
 *
 ****************************************************/
CHID MVIE::_ChidMvieNewSnd(void)
{
	AssertBaseThis(0);
	PCFL pcfl = _pcrfAutoSave->Pcfl();
	long ckid;
	long chid;
	KID kid;

	ckid = pcfl->Ckid(kctgMvie, _cno);
	for (chid = 0; chid < ckid; chid++)
		{
		if (!pcfl->FGetKidChidCtg(kctgMvie, _cno, chid, kctgMsnd, &kid))
			return (CHID)chid;
		}
	return (CHID)chid;
}


/****************************************************
 *
 * Verify the version number of a file
 *	 	
 * Parameters:
 *	pfni
 *	*pcno == cnoNil if using the first chunk in the file
 *
 * Returns:
 *  fFalse if there was a failure, else fTrue.
 *  *pcno updated
 *
 ****************************************************/
 bool MVIE::FVerifyVersion(PCFL pcfl, CNO *pcno)
 {
 	AssertBaseThis(0); // MVIE hasn't been loaded yet
	AssertPo(pcfl, 0);

	KID kid;
	CNO cnoMvie;
	MFP mfp;
	BLCK blck;

	// Get the cno of the first kid of the movie
	if (pvNil == pcno || cnoNil == *pcno)
		{
		if (!pcfl->FGetCkiCtg(kctgMvie, 0, &(kid.cki)))
			{
			PushErc(ercSocBadFile);
			return fFalse;
			}
		cnoMvie = kid.cki.cno;
		if (pvNil != pcno)
			*pcno = cnoMvie;
		}

	// Get version number of the file
	if (!pcfl->FFind(kctgMvie, cnoMvie, &blck) || !blck.FUnpackData() ||
		(blck.Cb() != size(MFP)) ||
		!blck.FReadRgb(&mfp, size(MFP), 0))
		{
        PushErc(ercSocBadFile);
		return fFalse;
		}

	if (mfp.bo == kboOther)
		{
		SwapBytesBom(&mfp, kbomMfp);
		}

	// Check the version numbers
	if (!mfp.dver.FReadable(kcvnCur, kcvnMin))
		{
        PushErc(ercSocBadVersion);
		return fFalse;
		}
	return fTrue;
}


/****************************************************
 *
 * Removes a scene from the movie.
 *
 * Parameters:
 *	iscen - Scene number to insert the new scene as.
 *
 * Returns:
 *  fFalse if there was a failure, else fTrue.
 *
 ****************************************************/
bool MVIE::FRemScenCore(long iscen)
{
	AssertThis(0);
	AssertIn(iscen, 0, Cscen());
	Assert(_pcrfAutoSave != pvNil, "Bad working file.");

	KID kid;
	PCFL pcfl;
	PSCEN pscen;
	long iscenOld;

	pcfl = _pcrfAutoSave->Pcfl();
	
	//
	// Ensure we are using the temp file
	//
	if (!_FUseTempFile())
		{
		return (fFalse);
		}

	//
	// Close this scene if it is open and different.
	//
    iscenOld = _iscen;
	if (_iscen != iscen)
		{

		//
		// Get the scen to remove.
		//
		if (!FSwitchScen(iscen))
			{
			FSwitchScen(iscenOld);
			return(fFalse);
			}

		}

    //
	// Keep the scene in memory for a second.
	//
	pscen = Pscen();
    pscen->AddRef();

	//
	// Close the current scene
	//
    SCEN::Close(&_pscenOpen);
	_iscen = ivNil;

	//
	// Remove its actors from the roll call
	//
    pscen->RemActrsFromRollCall();
    ReleasePpo(&pscen);

	//
	// Remove the scene chunk.
	//
	AssertDo(pcfl->FGetKidChidCtg(kctgMvie, _cno, iscen, kctgScen, &kid),
			 "Should never fail");
	pcfl->DeleteChild(kctgMvie, _cno, kctgScen, kid.cki.cno, iscen);

	//
	// Move up chids of all old scenes.	
	//
	_cscen--;
	_MoveChids((CHID)iscen, fFalse);

    //
	// Save changes, if this fails, we don't care.  It only
	// matters when the user tries to truly save.
	//
	pcfl->FSave(kctgSoc);

	//
	// Switch to a different scene
	//
	if (Cscen() == 0)
		{
		return(fTrue);
		}

	if (iscen < Cscen())
		{
		FSwitchScen(iscen);
		}
	else
		{
		FSwitchScen(iscen - 1);
		}

	SetDirty();
	
	if (Pscen() == pvNil)
		{
        PushErc(ercSocSceneSwitch);
		return(fTrue);
		}

	return(fTrue);
}


/****************************************************
 *
 * Removes a scene from the movie and creates an
 * undo object for the action.  If it was the
 * currently open scene, then the scene is not open.
 * The next scene is opened.
 *
 * Parameters:
 *	iscen - Scene number to remove.
 *
 * Returns:
 *  fFalse if there was a failure, else fTrue.
 *
 ****************************************************/
bool MVIE::FRemScen(long iscen)
{
	AssertThis(0);
	AssertIn(iscen, 0, Cscen());

	KID kid;
	PMUNS pmuns;
	PSCEN pscen;

	if (_iscen == iscen)
		{
		pscen = Pscen();
		pscen->AddRef();
		}
    else
		{

    	AssertDo(_pcrfAutoSave->Pcfl()->FGetKidChidCtg(kctgMvie, _cno, iscen, kctgScen, &kid),
				 "Should never fail");

    	pscen = SCEN::PscenRead(this, _pcrfAutoSave, kid.cki.cno);

		if ((pscen == pvNil) || !pscen->FPlayStartEvents())
			{
			SCEN::Close(&pscen);
			return(fFalse);
			}

		pscen->HideActors();
		pscen->HideTboxes();

		}

	pmuns = MUNS::PmunsNew();

	if (pmuns == pvNil)
		{
		ReleasePpo(&pscen);
		return(fTrue);
		}

	pmuns->SetIscen(iscen);
	pmuns->SetPscen(pscen);
	pmuns->SetMunst(munstRemScen);

	if (!FAddUndo(pmuns))
		{
		ReleasePpo(&pmuns);
		ReleasePpo(&pscen);
		return(fFalse);
		}

    ReleasePpo(&pmuns);
	ReleasePpo(&pscen);

	if (!FRemScenCore(iscen))
		{
        ClearUndo();
		return(fFalse);
		}

	return(fTrue);
}



/****************************************************
 *
 * Adds a new material to the user's document.
 *
 * Parameters:
 *	pmtrl - Pointer to the material to add.
 *  ptag - Pointer to the tag for the file.
 *
 * Returns:
 *  fTrue if success, fFalse if couldn't add the material
 *
 ****************************************************/
bool MVIE::FInsertMtrl(PMTRL pmtrl, PTAG ptag)
{
	AssertThis(0);
	AssertPo(pmtrl, 0);
	AssertVarMem(ptag);

    PCRF pcrf;
    PCFL pcfl;
	CNO cno;

	if (!FEnsureAutosave(&pcrf))
   		{
		TrashVar(ptag);
		return (fFalse);
		}

	pcfl = pcrf->Pcfl();
	if (!pmtrl->FWrite(pcfl, kctgMtrl, &cno))
		{
		TrashVar(ptag);
		return fFalse;
		}

	ptag->sid = ksidUseCrf;
	ptag->ctg = kctgMtrl;
	ptag->cno = cno;

	if (!TAGM::FOpenTag(ptag, _pcrfAutoSave))
		{
		return fFalse;
		}

	return fTrue;
}


/****************************************************
 *
 * Ensure an autosave file exists to use
 *
 * Parameters:
 *	Return the pcrf
 *
 * Returns:
 *  fTrue if success, fFalse if couldn't add the material
 *
 ****************************************************/
bool MVIE::FEnsureAutosave(PCRF *ppcrf)
{
	AssertThis(0);

    if (!_FMakeCrfValid())
		{
		return (fFalse);
		}

	if (pvNil != ppcrf)
		*ppcrf = _pcrfAutoSave;

	//
	// Switch to the autosave file
	//
	if (!_FUseTempFile())
		{
		return (fFalse);
		}
	return fTrue;
}


/****************************************************
 *
 * Adds a new 3-D Text object to the user's document.
 *
 * Parameters:
 *  pstn - TDT text
 *  tdts - the TDT shape
 *  ptagTdf - a tag to the TDT's font
 *
 * Returns:
 *  fTrue if success, fFalse if couldn't add the TDT
 *
 ****************************************************/
bool MVIE::FInsTdt(PSTN pstn, long tdts, PTAG ptagTdf)
{
	AssertThis(0);
	AssertPo(pstn, 0);
	Assert(pstn->Cch() > 0, "can't insert 0-length TDT");
	AssertIn(tdts, 0, tdtsLim);
	AssertVarMem(ptagTdf);

    PCFL pcfl;
	CNO cno;
	PTDT ptdt;
	TAG tagTdt;

	ptdt = TDT::PtdtNew(pstn, tdts, ptagTdf);
	if (pvNil == ptdt)
		return fFalse;

    //
	// Make sure we have a file to switch to
	//
    if (!_FMakeCrfValid())
		{
		return(fFalse);
		}

	pcfl = _pcrfAutoSave->Pcfl();

	//
	// Switch to the autosave file
	//
	if (!_FUseTempFile())
		{
		return (fFalse);
		}

	if (!ptdt->FWrite(pcfl, kctgTmpl, &cno))
		{
		return fFalse;
		}
	ReleasePpo(&ptdt);

	tagTdt.sid = ksidUseCrf;
	tagTdt.ctg = kctgTmpl;
	tagTdt.cno = cno;

	if (!TAGM::FOpenTag(&tagTdt, _pcrfAutoSave))
		{
		return fFalse;
		}

	if (!FInsActr(&tagTdt))
		return fFalse;

	TAGM::CloseTag(&tagTdt);

	return fTrue;
}


/****************************************************
 *
 * Edits the TDT attached to pactr.
 *
 * Parameters:
 *  pactr - Pointer to actor to change
 *  pstn - New TDT text
 *	tdts - New TDT shape
 *  ptagTdf - New TDT font
 *
 * Returns:
 *  fTrue if success, fFalse if couldn't change the TDT
 *
 ****************************************************/
bool MVIE::FChangeActrTdt(PACTR pactr, PSTN pstn, long tdts, PTAG ptagTdf)
{
	AssertThis(0);
	AssertPo(pactr, 0);
	Assert(pactr->Ptmpl()->FIsTdt(), "actor must be a TDT");
	AssertPo(pstn, 0);
	AssertIn(tdts, 0, tdtsLim);
	AssertVarMem(ptagTdf);

	long ich;
	bool fNonSpaceFound;
	PTDT ptdtNew;
	TAG tagTdtNew;
	PCFL pcfl;
	CNO cno;
	PACTR pactrDup;

	Assert(pactr == Pscen()->PactrSelected(), 0);
	fNonSpaceFound = fFalse;
	for (ich = 0; ich < pstn->Cch(); ich++)
		{
		if (pstn->Psz()[ich] != ChLit(' '))
			{
			fNonSpaceFound = fTrue;
			break;
			}
		}
	if (!fNonSpaceFound) // delete the actor
		{
		return FRemActr();
		}

	ptdtNew = TDT::PtdtNew(pstn, tdts, ptagTdf);
	if (pvNil == ptdtNew)
		return fFalse;

    //
	// Make sure we have a file to switch to
	//
    if (!_FMakeCrfValid())
		{
		return(fFalse);
		}

	pcfl = _pcrfAutoSave->Pcfl();

	//
	// Switch to the autosave file
	//
	if (!_FUseTempFile())
		{
		return (fFalse);
		}

	if (!ptdtNew->FWrite(pcfl, kctgTmpl, &cno))
		{
		return fFalse;
		}
	ReleasePpo(&ptdtNew);

	tagTdtNew.sid = ksidUseCrf;
	tagTdtNew.ctg = kctgTmpl;
	tagTdtNew.cno = cno;

	if (!TAGM::FOpenTag(&tagTdtNew, _pcrfAutoSave))
		{
		return fFalse;
		}

	if (!pactr->FDup(&pactrDup))
		{
		TAGM::CloseTag(&tagTdtNew);
		return fFalse;
		}

	if (!pactr->FChangeTagTmpl(&tagTdtNew))
		{
		TAGM::CloseTag(&tagTdtNew);
		ReleasePpo(&pactrDup);
		return fFalse;
		}

	// this sequence is a little strange to make unwinding easier:
	// we add the actor to the roll call with aridNil to get a new
	// entry, then remove the old entry (using pactrDup).
	pactr->SetArid(aridNil);
	if (!FAddToRollCall(pactr, pstn))
		{
		pactr->Restore(pactrDup);
		ReleasePpo(&pactrDup);
		TAGM::CloseTag(&tagTdtNew);
		return fFalse;
		}
	RemFromRollCall(pactrDup);
	ReleasePpo(&pactrDup);

	TAGM::CloseTag(&tagTdtNew);
	SetDirty();

	return fTrue;
}


/****************************************************
 *
 * Closes and releases the current scene, if any
 *
 * Parameters:
 *	None.
 *
 * Returns:
 *  fTrue if success, fFalse if couldn't autosave
 *
 ****************************************************/
bool MVIE::_FCloseCurrentScene(void)
{
	AssertThis(0);

	if (Pscen() != pvNil)
		{

		if (!FAutoSave(pvNil, fFalse)) // could not save...keep scene open
			{
			return fFalse;
			}

		SCEN::Close(&_pscenOpen);
		_iscen = ivNil;
		}
	return fTrue;
}


/****************************************************
 *
 * Makes sure that the current file in use is a temp file.
 *
 * Parameters:
 *	None.
 *
 * Returns:
 *  fTrue if success, fFalse if couldn't switch
 *
 ****************************************************/
bool MVIE::_FUseTempFile(void)
{
	AssertThis(0);

	PCFL pcfl;
	KID kid;
	FNI fni;

	pcfl = _pcrfAutoSave->Pcfl();

	//
	// Make sure we are using the temporary file
	//
	if (!pcfl->FTemp())
		{

        if (!fni.FGetTemp())
			{
			return(fFalse);
			}

		if (!pcfl->FSave(kctgSoc, &fni))
			{
			return(fFalse);
			}

		// Set the Temp flag
		AssertDo(pcfl->FSetGrfcfl(fcflTemp, fcflTemp), 0);

		//
		// Update _cno
		//
		AssertDo(pcfl->FGetCkiCtg(kctgMvie, 0, &(kid.cki)), "Should never fail");
        _cno = kid.cki.cno;

		}

	return fTrue;
}


/****************************************************
 *
 * Makes sure that there is a file to work with.
 *
 * Parameters:
 *	None.
 *
 * Returns:
 *  fTrue if success, fFalse if couldn't switch
 *
 ****************************************************/
bool MVIE::_FMakeCrfValid(void)
{
	AssertThis(0);

	PCFL pcfl;
	FNI fni;

	if (_pcrfAutoSave != pvNil)
		{
		return(fTrue);
		}

	//
	// Get a temp file
	//
	if (!fni.FGetTemp())
		{
		return(fFalse);
		}

	pcfl = CFL::PcflCreate(&fni, fcflTemp);
	if (pcfl == pvNil)
		{
		return(fFalse);
		}

    Assert(pcfl->FTemp(), "Bad CFL");

	//
	// Note (by *****): CRF *must* have 0 cache size, because of
	// serious cache-coherency problems otherwise.  TMPL data is not
	// read-only, and chunk numbers change over time.
	//
	_pcrfAutoSave = CRF::PcrfNew(pcfl, 0);  // cache size must be 0
	if (pvNil == _pcrfAutoSave)
		{
		ReleasePpo(&pcfl);
		return(fFalse);
		}

    //
	// Create movie chunk
	//
	SetDirty();
	if (!FAutoSave())
		{
		ReleasePpo(&pcfl);
		return(fFalse);
		}

	ReleasePpo(&pcfl);
	return fTrue;
}


/****************************************************
 *
 * Saves a movie to the temp file assigned to the movie.
 *
 * Parameters:
 *	pfni - File to save to, pvNil if to use a temp file.
 *	pCleanRollCall - Should actors that are not used be removed?
 *
 * Returns:
 *  fFalse if there was a failure, else fTrue.
 *
 ****************************************************/
bool MVIE::FAutoSave(PFNI pfni, bool fCleanRollCall)
{
	AssertThis(0);
	AssertNilOrPo(_pcrfAutoSave, 0);
	AssertNilOrPo(pfni, ffniFile);

#ifdef BUG1848
	bool fRetry = fTrue;
#endif // BUG1848
	BLCK blck;
	CNO cno;
	CNO cnoScen;
	CNO cnoSource;
	MFP mfp;
	KID kidScen, kidGstRollCall, kidGstSource;
	PCFL pcfl;
	PGST pgstSource = pvNil;

	if (_pcrfAutoSave == pvNil)
		{
		return(fFalse);
		}

	pcfl = _pcrfAutoSave->Pcfl();

	//
	// If no changes, then quit quick.
	//
	if (!_fAutosaveDirty && (pfni == pvNil) && !_fDocClosing)
		{
		return(fTrue);
		}

	vpappb->BeginLongOp();

	if ((pfni == pvNil) && !pcfl->FTemp() && !_FUseTempFile())
		{
		vpappb->EndLongOp();
		return(fFalse);
		}

#ifdef BUG1848
LRetry:
#endif // BUG1848
	//
	// Ensure movie chunk exists.
	//
	if (_cno == cnoNil)
		{
		if (!pcfl->FAdd(size(MFP), kctgMvie, &_cno, &blck))
			{
			goto LFail0;
			}

        mfp.bo = kboCur;
		mfp.osk= koskCur;
		mfp.dver.Set(kcvnCur, kcvnBack);

		if (!blck.FWrite(&mfp))
			{
			goto LFail0;
			}

   		}

	//
	// Save open scene.
	//
	if (Pscen() != pvNil)
		{

		//
		// Save scene in new chunk
		//
		if (!Pscen()->FWrite(_pcrfAutoSave, &cnoScen))
			{
			goto LFail0;
			}

        //
		// Delete old chunk with this scene
		//
		AssertDo(pcfl->FGetKidChidCtg(kctgMvie, _cno, _iscen, kctgScen,
			&kidScen), "Should never fail");

        //
		// Update chid for movie
		//
	    if (!pcfl->FAdoptChild(kctgMvie, _cno, kctgScen, cnoScen, _iscen))
			{
			pcfl->Delete(kctgScen, cnoScen);
			goto LFail0;
			}

		}

    //
	// Save the movie roll-call
	//
    if (fCleanRollCall)
		{
        MACTR mactr;
		long imactr;

		for (imactr = 0; imactr < _pgstmactr->IvMac();)
			{
            _pgstmactr->GetExtra(imactr, &mactr);
			if (mactr.cactRef == 0)
				{
                _pgstmactr->Delete(imactr);
				vptagm->CloseTag(&mactr.tagTmpl);
				}
            else
				{
                imactr++;
				}
			}

		_pmcc->UpdateRollCall();

		}

    //
	// Get old roll call if it exists.
	//
	if (!pcfl->FGetKidChidCtg(kctgMvie, _cno, 0, kctgGst, &kidGstRollCall))
		{
		kidGstRollCall.cki.cno = cnoNil;
		}

	if (!pcfl->FAdd(_pgstmactr->CbOnFile(), kctgGst, &cno, &blck))
		{
		goto LFail1;
		}

    if (!_pgstmactr->FWrite(&blck) ||
			!pcfl->FAdoptChild(kctgMvie, _cno, kctgGst, cno, 0))
		{
		pcfl->Delete(kctgGst, cno);
		goto LFail1;
		}

	//
	// Save the known sources list
	//

    //
	// Get old sources list if it exists.
	//
	if (!pcfl->FGetKidChidCtg(kctgMvie, _cno, kchidGstSource, kctgGst,
			&kidGstSource))
		{
		kidGstSource.cki.cno = cnoNil;
		}

	pgstSource = vptagm->PgstSource();
	if (pgstSource == pvNil)
		goto LFail2;

	if (!pcfl->FAdd(pgstSource->CbOnFile(), kctgGst, &cnoSource, &blck))
		{
		goto LFail2;
		}

    if (!pgstSource->FWrite(&blck) || !pcfl->FAdoptChild(kctgMvie, _cno,
			kctgGst, cnoSource, kchidGstSource))
		{
		pcfl->Delete(kctgGst, cnoSource);
		goto LFail2;
		}

	if (!pcfl->FSetName(kctgMvie, _cno, &_stnTitle))
		{
		goto LFail3;
		}

	//
	// Delete old scene if there is a scene.
	//
	if (Pscen() != pvNil)
		{
		pcfl->DeleteChild(kctgMvie, _cno, kctgScen, kidScen.cki.cno, _iscen);
		}

    //
	// Delete old roll call list if it exists.
	//
	if (kidGstRollCall.cki.cno != cnoNil)
		{
		pcfl->DeleteChild(kctgMvie, _cno, kctgGst, kidGstRollCall.cki.cno, 0);
		}

    //
	// Delete old sources list if it exists.
	//
	if (kidGstSource.cki.cno != cnoNil)
		{
		pcfl->DeleteChild(kctgMvie, _cno, kctgGst, kidGstSource.cki.cno,
			kchidGstSource);
		}

    //
	// If we fail, don't unwind, as everything is consistent.
	// Just let the client know we failed.
	//
	if (pfni != pvNil)
    	{
		bool fSuccess;
		PFIL pfil;
    	
		//
		// If we have this file open, then we need to release it
		// so we can do the save.  We will restore our open below.
		//
		pfil = FIL::PfilFromFni(pfni);
		if (pfil == _pfilSave)
			{
			ReleasePpo(&_pfilSave);
			_fFniSaveValid = fFalse;
			}
		else
			{
			pfil = pvNil;
			}
		//
		// All garbage collection -- ignore any errors, the file will just be
		// a widdle bigger than it has to be.
		//
		_FDoGarbageCollection(pcfl);
		
		fSuccess = pcfl->FSave(kctgSoc, pfni);

		if (pfil != pvNil)
			{
			_pfilSave = FIL::PfilFromFni(pfni);
			if (_pfilSave != pvNil)
				{
				_pfilSave->AddRef();
				_fFniSaveValid = fTrue;
				}
			}

		if (!fSuccess)
			{
			goto LFail0;
			}
		}
	else if (!pcfl->FSave(kctgSoc))
		{
		goto LFail0;
		}

    _fAutosaveDirty = fFalse;
	_fDirty = fTrue;

    //
	// Set the movie title
	//
	_SetTitle(pfni);

	vpappb->EndLongOp();
	return(fTrue);


LFail3:
	pcfl->DeleteChild(kctgMvie, _cno, kctgGst, cnoSource, kchidGstSource);

LFail2:
	pcfl->DeleteChild(kctgMvie, _cno, kctgGst, cno, 0);

LFail1:
	if (Pscen() != pvNil)
		{
		pcfl->DeleteChild(kctgMvie, _cno, kctgScen, cnoScen, _iscen);
		}

LFail0:
	if (pcfl->ElError() != elNil)
		{
		pcfl->ResetEl();
#ifdef BUG1848
		if (fRetry && pfni != pvNil)
			{
			FNI fniTemp;

			/* Effectively, move the temp file to the destination path */
			/* REVIEW seanse(peted): note that the autosave file could whined up
				on the floppy if we fail again (which SeanSe says is "Bad") */
			fniTemp = *pfni;
			if (fniTemp.FGetUnique(pfni->Ftg()) &&
					pcfl->FSave(kctgSoc, &fniTemp))
				{
				pcfl->SetTemp(fTrue);
				vpers->Clear();
				fRetry = fFalse;
				goto LRetry;
				}
			}
#endif // BUG1848
		PushErc(ercSocSaveFailure);
		}

	vpappb->EndLongOp();
	return(fFalse);
}


/****************************************************
 *
 * Do all garbage collection
 *
 * Parameters:
 *	pfni - File to remove chunks from
 *
 * Returns:
 *  fTrue on success, fFalse on failure
 *
 ****************************************************/
bool MVIE::_FDoGarbageCollection(PCFL pcfl)
{
	AssertThis(0);
	AssertPo(pcfl, 0);
	bool fSuccess, fHaveValid;

	// Material and Template garbage collection
	fSuccess = _FDoMtrlTmplGC(pcfl);
	
	// If closing, remove unused sounds
	if (_fDocClosing && FUnusedSndsUser(&fHaveValid))
		_DoSndGarbageCollection(!fHaveValid || Pmcc()->FQueryPurgeSounds());
	return fSuccess;
}


/****************************************************
 *
 * Removes all MTRL and TMPL chunks that are not
 * referenced by any actors in this movie.
 *
 * Parameters:
 *	pfni - File to remove chunks from
 *
 * Returns:
 *  fTrue on success, fFalse on failure
 *
 ****************************************************/
bool MVIE::_FDoMtrlTmplGC(PCFL pcfl)
{
	AssertThis(0);
	AssertPo(pcfl, 0);

	PTAGL ptagl = pvNil;
	long itag;
	TAG tag;
	long icki1 = 0;
	long icki2 = 0;
	CKI cki;
	PGL pglckiDoomed = pvNil;

	ptagl = _PtaglFetch();		// get all tags in user's document
	if (ptagl == pvNil)
		goto LEnd; // no work to do

	pglckiDoomed = GL::PglNew(size(CKI), 0);
	if (pvNil == pglckiDoomed)
		goto LFail;

	while (pcfl->FGetCkiCtg(kctgMtrl, icki1++, &cki) ||
		pcfl->FGetCkiCtg(kctgTmpl, icki2++, &cki))
		{
		// We're only interested in ksidUseCrf tags
		for (itag = 0; itag < ptagl->Ctag(); itag++)
			{
			ptagl->GetTag(itag, &tag);
			if (tag.sid != ksidUseCrf)
				{
				break; // stop..we're out of the ksidUseCrf tags
				}
			if (tag.ctg == cki.ctg && tag.cno == cki.cno)
				{
				break; // stop..this tag is used in the movie
				}
			}
		// Remember, tags are sorted by sid.  So if we got past the
		// ksidUseCrf tags in the movie, this chunk must not be used
		// in the movie.  So put it on the blacklist.
		if (tag.sid != ksidUseCrf || itag == ptagl->Ctag())
			{
			// this chunk is not referenced by a ksidUseCrf tag, so kill it
			if (!pglckiDoomed->FAdd(&cki))
				goto LFail;
			}
		}
	// Get rid of the blacklisted chunks
	for (icki1 = 0; icki1 < pglckiDoomed->IvMac(); icki1++)
		{
		pglckiDoomed->Get(icki1, &cki);
		pcfl->Delete(cki.ctg, cki.cno);
		if (pcfl == _pcrfAutoSave->Pcfl()) // remove chunk from CRF cache
			{
			PFNRPO pfnrpo;

			if (kctgMtrl == cki.ctg)
				{
				pfnrpo = MTRL::FReadMtrl;
				}
			else if (kctgTmpl == cki.ctg)
				{
				pfnrpo = TMPL::FReadTmpl;
				}
			else
				{
				Bug("unexpected ctg");
				}
			// ignore failure of FSetCrep, because return value of fFalse
			// just means that the chunk is not stored in the CRF's cache
			_pcrfAutoSave->FSetCrep(crepToss, cki.ctg, cki.cno, pfnrpo);
			}
		}
LEnd:
	ReleasePpo(&ptagl);
	ReleasePpo(&pglckiDoomed);
	return fTrue;
LFail:
	ReleasePpo(&ptagl);
	ReleasePpo(&pglckiDoomed);
	return fFalse;
}



/****************************************************
 *
 * Gets the file name to save the document to.
 *
 * Parameters:
 *	pfni - A pointer to a place to store the name
 *
 * Returns:
 *  fFalse if the fni is valid, else fTrue *and*
 *  pfni filled in.
 *
 ****************************************************/
bool MVIE::FGetFni(FNI *pfni)
{
	AssertThis(0);
	AssertPo(pfni, 0);

	if (_pfilSave != pvNil)
		{
        _pfilSave->GetFni(pfni);
		}

    return (_fFniSaveValid);
}

/***************************************************************************
 *
 * Saves a movie.
 *
 * Parameters:
 *  cid - type of save command issued for save
 *
 * Returns:
 *  fFalse if there was a failure, else fTrue.
 *
 ****************************************************/
bool MVIE::FSave(long cid)
{
	AssertThis(0);

	// If we are processing a Save command and the current movie is read-only,
	// then treat this as a Save-As command and invoke the Save portfolio.

	if (cid == cidSave && FReadOnly())
		cid = cidSaveAs;

	// Now take the default action.
	return MVIE_PAR::FSave(cid);
}

/****************************************************
 *
 * Saves a movie to the given fni.
 *
 * Parameters:
 *	pfni - File to write to.
 *  fSetFni - Should the file name be remembered.
 *
 * Returns:
 *  fFalse if there was a failure, else fTrue.
 *
 ****************************************************/
bool MVIE::FSaveToFni(FNI *pfni, bool fSetFni)
{
	AssertThis(0);
	AssertNilOrPo(pfni, ffniFile);

	CKI cki;
	PCFL pcfl;

	if (_pcrfAutoSave == pvNil)
		{
		return(pvNil);
		}

	pcfl = _pcrfAutoSave->Pcfl();

	//
	// Update the file
	//
	if (!FAutoSave(pfni, fTrue))
		{
		return(fFalse);
		}

    ClearUndo();

	// Set the AddToExtra flag and clear the Temp flag
    AssertDo(pcfl->FSetGrfcfl(fcflAddToExtra, fcflAddToExtra | fcflTemp), 0);

	if (fSetFni)
		{
		_FSetPfilSave(pfni);  // Ignore failure
		}

	//
	// Update _cno
	//
	AssertDo(pcfl->FGetCkiCtg(kctgMvie, 0, &cki), "Should never fail");

    _cno = cki.cno;

	_fDirty = fFalse;

	return(fTrue);
}

/***************************************************************************
 *
 * Called by docb.cpp to get an fni for a selected movie file using
 * the save portfolio.
 *
 * Parameters:
 *	pfni - fni for selected file
 *
 * Returns:
 *  TRUE  - User selected a file
 *  FALSE - User canceled, (or other error).
 *
***************************************************************************/
bool MVIE::FGetFniSave(FNI *pfni)
{
	AssertThis(0);
	AssertVarMem(pfni);

	return( _pmcc->GetFniSave(pfni, idsPortfMovieFilterLabel, idsPortfMovieFilterExt, idsPortfSaveMovieTitle, ksz3mm, &_stnTitle));
}

/****************************************************
 *
 * Creates a new view on a movie.
 *
 * Parameters:
 *	pgcb - The creation block describing the gob placement
 *
 * Returns:
 *  A pointer to the view, otw pvNil on failure
 *
 ****************************************************/
PDDG MVIE::PddgNew(PGCB pgcb)
{
	AssertThis(0);
	AssertVarMem(pgcb);
	return(MVU::PmvuNew(this, pgcb, _pmcc->Dxp(), _pmcc->Dyp()));
}

/****************************************************
 *
 * Is not used.  Not supported.  Stubbed out here for
 * debugging.
 *
 * Parameters:
 *	None
 *
 * Returns:
 *  pvNil
 *
 ****************************************************/
PDMD MVIE::PdmdNew(void)
{
	Bug("Movie does not support DMDs, use multiple DDGs.");
	return(pvNil);
}


/****************************************************
 *
 * Adds a single item to the undo list
 *
 * Parameters:
 *	pmund - A pointer to a movie undo item.
 *
 * Returns:
 *  fTrue if successful, else fFalse.
 *
 ****************************************************/
bool MVIE::FAddUndo(PMUNB pmunb)
{
	AssertThis(0);

	pmunb->SetPmvie(this);
	pmunb->SetIscen(Iscen());

	AssertPo(pmunb, 0);

	if (Iscen() != ivNil)
		{
		pmunb->SetNfrm(Pscen()->Nfrm());
		}

    if (!DOCB::FAddUndo(pmunb))
		{
		Pmcc()->SetUndo(undoDisabled);
		return(fFalse);
		}

    Pmcc()->SetUndo(undoUndo);
	return(fTrue);
}


/****************************************************
 *
 * Clears out the undo buffer
 *
 * Parameters:
 *	None.
 *
 * Returns:
 *  None.
 *
 ****************************************************/
void MVIE::ClearUndo(void)
{
	AssertThis(0);

	MVIE_PAR::ClearUndo();
	Pmcc()->SetUndo(undoDisabled);
}

/***************************************************************************
 *
 * This routine changes the current camera view
 *
 * Parameters:
 *	icam - The new camera to use.
 *
 * Returns:
 *  fTrue if successful, else fFalse.
 *
 **************************************************************************/
bool MVIE::FChangeCam(long icam)
{
	AssertThis(0);
	AssertIn(icam, 0, kccamMax);
	AssertPo(Pscen(), 0);

	if (!Pscen()->FChangeCam(icam))
		{
		return(fFalse);
		}

    SetDirty();
	InvalViews();
	return(fTrue);
}

/***************************************************************************
 *
 * This command inserts a new text box into the open scene.
 *
 * Parameters:
 *	prc - The placement within the movie's view of the text box.
 *  fStory - Is this supposed to be a story text box?
 *
 * Returns:
 *  fTrue if successful, else fFalse.
 *
 **************************************************************************/
bool MVIE::FInsTbox(RC *prc, bool fStory)
{
	AssertThis(0);
	AssertPvCb(prc, size(RC));
	AssertPo(Pscen(), 0);

	PTBOX ptbox;

	ptbox = TBOX::PtboxNew(Pscen(), prc, fStory);

	if (ptbox == pvNil)
		{
		return(fFalse);
		}
	ptbox->SetDypFontDef(Pmcc()->DypTboxDef());

	AssertPo(ptbox, 0);

	if (!Pscen()->FAddTbox(ptbox))
		{
        ReleasePpo(&ptbox);
		}

    Pscen()->SelectTbox(ptbox);
	ptbox->AttachToMouse();
	ReleasePpo(&ptbox);

	return(fTrue);
}


/***************************************************************************
 *
 * This command removes the currently selected text box.
 *
 * Parameters:
 *	None.
 *
 * Returns:
 *  fTrue if successful, else fFalse.
 *
 **************************************************************************/
bool MVIE::FNukeTbox(void)
{
	AssertThis(0);
	AssertPo(Pscen(), 0);

	PTBOX ptbox;

	ptbox = Pscen()->PtboxSelected();

	if (ptbox == pvNil)
		{
        PushErc(ercSocNoTboxSelected);
		return(fFalse);
		}

	return(Pscen()->FRemTbox(ptbox));
}

/***************************************************************************
 *
 * This command hides the currently selected text box at the current frame.
 *
 * Parameters:
 *	None.
 *
 * Returns:
 *  fTrue if successful, else fFalse.
 *
 **************************************************************************/
bool MVIE::FHideTbox(void)
{
	AssertThis(0);
	AssertPo(Pscen(), 0);

	PTBOX ptbox;

	ptbox = Pscen()->PtboxSelected();

	if (ptbox == pvNil)
		{
        PushErc(ercSocNoTboxSelected);
		return(fFalse);
		}

	return(ptbox->FHide());
}


/***************************************************************************
 *
 * This command selects the itbox'th text box in the current frame.
 *
 * Parameters:
 *	itbox - Index value of the text box to select.
 *
 * Returns:
 *  None.
 *
 **************************************************************************/
void MVIE::SelectTbox(long itbox)
{
	AssertThis(0);
	AssertPo(Pscen(), 0);

	PTBOX ptbox;
	long cVis = 0;

    while (fTrue)
		{
		ptbox = Pscen()->PtboxFromItbox(itbox);
    	if (ptbox == pvNil)
    		{
            return;
    		}

        if (ptbox->FIsVisible() && (cVis == itbox))
			{
            return;
			}

        if (ptbox->FIsVisible())
			{
			cVis++;
			}

		}


    Pscen()->SelectTbox(ptbox);
}

/***************************************************************************
 *
 * This sets the color to apply to a text box.
 *
 * Parameters:
 *  acr - The destination color.
 *
 * Returns:
 *  fTrue if successful, else fFalse.
 *
 **************************************************************************/
void MVIE::SetPaintAcr(ACR acr)
{
	AssertThis(0);

	PMVU pmvu;

	pmvu = (PMVU)PddgGet(0);
	AssertPo(pmvu, 0);
	pmvu->SetPaintAcr(acr);
}


/******************************************************************************
	SetDypFontTextCur
		Sets the current textbox font size
	
	Arguments:
		long dypFont  --  the new textbox font size
	
************************************************************ PETED ***********/
void MVIE::SetDypFontTextCur(long dypFont)
{
	AssertThis(0);

	PmvuFirst()->SetDypFontTextCur(dypFont);
}


/******************************************************************************
	SetStyleTextCur
		Sets the current textbox font style
	
	Arguments:
		long grfont  --  the new textbox font style
	
************************************************************ PETED ***********/
void MVIE::SetStyleTextCur(ulong grfont)
{
	AssertThis(0);

	PmvuFirst()->SetStyleTextCur(grfont);
}


/******************************************************************************
	SetOnnTextCur
		Sets the current textbox font face
	
	Arguments:
		long onn  -- the new textbox font face
	
************************************************************ PETED ***********/
void MVIE::SetOnnTextCur(long onn)
{
	AssertThis(0);

	PmvuFirst()->SetOnnTextCur(onn);
}


/******************************************************************************
	PmvuCur
		Returns the active MVU for this movie
	
************************************************************ PETED ***********/
PMVU MVIE::PmvuCur(void)
{
	AssertThis(0);
	PMVU pmvu = (PMVU)PddgActive();

	AssertPo(pmvu, 0);
	Assert(pmvu->FIs(kclsMVU), "Current DDG isn't an MVU");
	return pmvu;
}


/******************************************************************************
	PmvuFirst
		Returns the first MVU for this movie
	
************************************************************ PETED ***********/
PMVU MVIE::PmvuFirst(void)
{
	AssertThis(0);
	PMVU pmvu = (PMVU)PddgGet(0);

	AssertPo(pmvu, 0);
	Assert(pmvu->FIs(kclsMVU), "First DDG isn't an MVU");
	return pmvu;
}


/***************************************************************************
 *
 * This command inserts a new actor.
 *
 * Parameters:
 *	ptag - The tag of the actor to create.
 *
 * Returns:
 *  fTrue if successful, else fFalse.
 *
 **************************************************************************/
bool MVIE::FInsActr(PTAG ptag)
{
	AssertThis(0);
	AssertPvCb(ptag, size(TAG));
	AssertPo(Pscen(), 0);

	PACTR pactr;


	vpappb->BeginLongOp();

	//
	// Create the actor.
	//
    if (!vptagm->FCacheTagToHD(ptag))
		{
		vpappb->EndLongOp();
		return(fFalse);
		}
	
	vpappb->EndLongOp();

	pactr = ACTR::PactrNew(ptag);
	if (pactr == pvNil)
		{
		return(fFalse);
		}

	AssertPo(pactr, 0);
	if (!Pscen()->FAddActr(pactr))
		{
		ReleasePpo(&pactr);
		return(fFalse);
		}

	ReleasePpo(&pactr);

	SetDirty();
	InvalViewsAndScb();

	return(fTrue);
}

/***************************************************************************
 *
 * Brings an actor on to the stage.
 *
 * Parameters:
 *	arid - Arid of the actor to bring on.  aridNil implies the
 *		currently selected actor.
 *
 * Returns:
 *  fTrue if successful, else fFalse.
 *
 **************************************************************************/
bool MVIE::FAddOnstage(long arid)
{
	AssertThis(0);
	AssertPo(Pscen(), 0);

	ACTR *pactr;
	//
	// Get the actor
	//
	if (arid == aridNil)
		{
		pactr = Pscen()->PactrSelected();
		}
    else
		{
        pactr = Pscen()->PactrFromArid(arid);
		Pscen()->SelectActr(pactr);
		}

    if (pactr == pvNil)
		{
        PushErc(ercSocNoActrSelected);
		return(fFalse);
		}

    AssertPo(pactr, 0);

	if (!pactr->FAddOnStage())
		{
		return(fFalse);
		}

    SetDirty();
	InvalViews();

	return(fTrue);
}

/***************************************************************************
 *
 * Removes the selected actor from the current scene
 *
 * Parameters:
 *	None.
 *
 * Returns:
 *  fTrue if successful, else fFalse.
 *
 **************************************************************************/
bool MVIE::FRemActr()
{
	AssertThis(0);
	AssertPo(Pscen(), 0);

	ACTR *pactr;

	//
	// Get the current actor
	//
	pactr = Pscen()->PactrSelected();
	AssertPo(pactr, 0);
	if (!Pscen()->FRemActr(pactr->Arid()))
		{
        return(fFalse);
		}

	SetDirty();
	InvalViewsAndScb();
	Pmcc()->ActorNuked();

	return(fTrue);
}

/***************************************************************************
 *
 * Rotates selected actor by degrees around an axis.
 *
 * Parameters:
 *  axis - Brender axis to rotate around.
 *	xa - Degrees to rotate by in X.
 *	ya - Degrees to rotate by in Y.
 *	za - Degrees to rotate by in Z.
 *
 * Returns:
 *  fTrue if successful, else fFalse.
 *
 **************************************************************************/
bool MVIE::FRotateActr(BRA xa, BRA ya, BRA za, bool fFromHereFwd)
{
	AssertThis(0);
	AssertPo(Pscen(), 0);

	ACTR *pactr;

	//
	// Get the current actor
	//
	pactr = Pscen()->PactrSelected();
	AssertPo(pactr, 0);

	if (!pactr->FRotate(xa, ya, za, fFromHereFwd))
		{
		return(fFalse);
		}

    SetDirty();
	InvalViews();

	return(fTrue);
}

/***************************************************************************
 *
 * Squashes/Stretches the selected actor by a scalar.
 *
 * Parameters:
 *  brs - The scalar for squashing.
 *
 * Returns:
 *  fTrue if successful, else fFalse.
 *
 **************************************************************************/
bool MVIE::FSquashStretchActr(BRS brs)
{
	AssertThis(0);
	AssertPo(Pscen(), 0);

	ACTR *pactr;

	//
	// Get the current actor
	//
	pactr = Pscen()->PactrSelected();
	AssertPo(pactr, 0);

	if (brs == rZero)
		{
		return(fTrue);
		}

	BRS brsy = BrsDiv(rOne, brs);

	if (!pactr->FPull(brs, brsy, brs))
		{
		return(fFalse);
		}

    SetDirty();
	InvalViews();

	return(fTrue);
}

/***************************************************************************
 *
 * Drags the actor in time
 *
 * Parameters:
 *  nfrm - The destination frame number.
 *
 * Returns:
 *  fTrue if successful, else fFalse.
 *
 **************************************************************************/
bool MVIE::FSoonerLaterActr(long nfrm)
{
	AssertThis(0);
	AssertPo(Pscen(), 0);

	ACTR *pactr;
	PAUND paund;

	long dnfrm = nfrm - Pscen()->Nfrm();

	//
	// Get the current actor
	//
	pactr = Pscen()->PactrSelected();
	AssertPo(pactr, 0);

	if (!pactr->FSoonerLater(dnfrm))
		{
		return(fFalse);
		}

    if (CundbUndo() > 0)
		{

		_pglpundb->Get(_ipundbLimDone - 1, &paund);

    	if (paund->FIs(kclsAUND) && paund->FSoonerLater())
    		{
    		AssertPo(paund, 0);
    		paund->SetNfrmLast(nfrm);
    		}

		}

    SetDirty();
	InvalViews();

	return(fTrue);
}


/***************************************************************************
 *
 * Scales the selected actor by a scalar.
 *
 * Parameters:
 *  brs - The scalar for scaling.
 *
 * Returns:
 *  fTrue if successful, else fFalse.
 *
 **************************************************************************/
bool MVIE::FScaleActr(BRS brs)
{
	AssertThis(0);
	AssertPo(Pscen(), 0);

	ACTR *pactr;

	//
	// Get the current actor
	//
	pactr = Pscen()->PactrSelected();
	AssertPo(pactr, 0);

	if (!pactr->FScale(brs))
		{
		return(fFalse);
		}

    SetDirty();
	InvalViewsAndScb();

	return(fTrue);
}


/****************************************************
 *
 * Adds a sound to the background
 *
 * Parameters:
 *  ptag - tag to the MSND to insert
 *  fLoop - play snd over and over?
 *  fQueue - replace existing sounds, or queue afterwards?
 *  vlm - volume to play sound
 *  sty - sound type
 *
 * Returns:
 *  fFalse if there was a failure, else fTrue.
 *
 ****************************************************/
bool MVIE::FAddBkgdSnd(PTAG ptag, bool fLoop, bool fQueue, long vlm, long sty)
{
	AssertThis(0);
	Assert(Pscen(), 0);

	if (vlm == vlmNil || sty == styNil)
		{
		PMSND pmsnd;
	
		pmsnd = (PMSND)vptagm->PbacoFetch(ptag, MSND::FReadMsnd);
		if (pmsnd == pvNil)
			return fFalse;
		if (vlm == vlmNil)
			vlm = pmsnd->Vlm();
		if (sty == styNil)
			sty = pmsnd->Sty();
		ReleasePpo(&pmsnd);
		}

	return Pscen()->FAddSnd(ptag, fLoop, fQueue, vlm, sty);
}


/****************************************************
 *
 * Adds a sound to an actor
 *
 * Parameters:
 *  pactr - actor to attach sound to
 *  ptag - tag to the MSND to insert
 *  fLoop - play snd over and over?
 *  fQueue - replace existing sounds, or queue afterwards?
 *  vlm - volume to use (vlmNil -> use pmsnd volume)
 *  sty - type to use (styNil -> use pmsnd sty)
 *
 * Returns:
 *  fFalse if there was a failure, else fTrue.
 *
 ****************************************************/
bool MVIE::FAddActrSnd(PTAG ptag, bool fLoop, bool fQueue, bool fActnCel,
	long vlm, long sty)
{
	AssertThis(0);
	ACTR *pactr;

	//
	// Get the current actor
	//
	pactr = Pscen()->PactrSelected();
	AssertPo(pactr, 0);

	return pactr->FSetSnd(ptag, fLoop, fQueue, fActnCel, vlm, sty);
}


/****************************************************
 *
 * Takes a scene and inserts it as scene number iscen,
 * and switches to the scene.
 *
 * Parameters:
 *	iscen - Scene number to insert the new scene as.
 *	pscen - Pointer to the scene to insert.
 *
 * Returns:
 *  fFalse if there was a failure, else fTrue.
 *
 ****************************************************/
bool MVIE::FInsScenCore(long iscen, SCEN *pscen)
{
	AssertThis(0);
	AssertIn(iscen, 0, Cscen() + 1);
	AssertPo(pscen, 0);
	Assert(pscen->Pmvie() == this, "Cannot insert a scene from another movie");

	CNO cnoScen;
	PCFL pcfl;

    //
	// Make sure we have a file to switch to
	//
    if (!_FMakeCrfValid())
		{
		goto LFail0;
		}

	pcfl = _pcrfAutoSave->Pcfl();

	//
	// Ensure we are using the temp file
	//
	if (!_FUseTempFile())
		{
		goto LFail0;
		}

	//
	// Save old scene.
	//
	if (!_FCloseCurrentScene())
		{
		goto LFail0;
		}

    //
	// Write it.
	//
    if (!pscen->FWrite(_pcrfAutoSave, &cnoScen))
		{
		goto LFail0;
		}

	//
	// Hide all bodies, textboxes, etc, created when the write updated the thumbnail.
	//
	pscen->AddRef();
	SCEN::Close(&pscen);

    _MoveChids((CHID)iscen, fTrue);

    _cscen++;

	//
	// Insert new scene as chid.
	//
	if (!pcfl->FAdoptChild(kctgMvie, _cno, kctgScen, cnoScen, iscen))
		{
		goto LFail2;
		}

    //
	// Save changes, if this fails, we don't care.  It only
	// matters when the user tries to truly save.
	//
	pcfl->FSave(kctgSoc);

	if (!FSwitchScen(iscen))
		{
		goto LFail3;
		}

	//
	// Fix up roll call, don't care about failure -- roll-call will just be messed up.
	//
	Pscen()->FAddActrsToRollCall();

    SetDirty();
	_pmcc->UpdateRollCall();

	if (FSoundsEnabled())
		{
		_pmsq->PlayMsq();
		}
	else
		{
		_pmsq->FlushMsq();
		}

	return(fTrue);

LFail3:
	_cscen--;
	pcfl->DeleteChild(kctgMvie, _cno, kctgScen, cnoScen, iscen);
	_MoveChids((CHID)iscen, fFalse);
	pcfl->FSave(kctgSoc);

	if (_cscen > 0)
		{

        if (iscen < _cscen)
			{
			FSwitchScen(iscen);
			}
		else
			{
			FSwitchScen(_cscen - 1);
			}

		}

	return(fFalse);

LFail2:
	_MoveChids((CHID)iscen, fFalse);
	pcfl->Delete(kctgScen, cnoScen);

LFail0:
	return(fFalse);
}


/***************************************************************************
 *
 * Adds a new scene after the current scene.
 *
 * Parameters:
 *	ptag - The tag of the scene to add.
 *
 * Returns:
 *  fTrue if successful, else fFalse.
 *
 **************************************************************************/
bool MVIE::FAddScen(PTAG ptag)
{
	AssertThis(0);
	AssertPvCb(ptag, size(TAG));

	long iscen;
 	TAG tagOld;
	PMUNS pmuns;

	vpappb->BeginLongOp();

	//
	// Set the background
	//
	if (!BKGD::FCacheToHD(ptag))
		{
		vpappb->EndLongOp();
		return(fFalse);
		}

	vpappb->EndLongOp();

	//
	// Set default name
	//
	if ((Pscen() == pvNil) && (_stnTitle.Cch() == 0))
		{
		_SetTitle();
		}

	//
	// Check if this is supposed to overwrite the current scene
	//
	if ((Pscen() == pvNil) || (!Pscen()->FIsEmpty()))
		{

        iscen = Iscen();
		if (iscen == ivNil)
			{
			iscen = -1;
			}


   		if (!FNewScenInsCore(iscen + 1))
			{
			return(fFalse);
			}

		if (!Pscen()->FSetBkgdCore(ptag, &tagOld))
			{
            FRemScenCore(iscen + 1);
			return(fFalse);
			}

		if (FSoundsEnabled())
			{
			_pmsq->PlayMsq();
			}
		else
			{
			_pmsq->FlushMsq();
			}

		pmuns = MUNS::PmunsNew();

		if (pmuns != pvNil)
			{
    		pmuns->SetMunst(munstInsScen);
    		pmuns->SetIscen(iscen + 1);
			pmuns->SetTag(ptag);

        	if (!FAddUndo(pmuns))
        		{
				ReleasePpo(&pmuns);
				FRemScenCore(iscen + 1);
				return(fFalse);
        		}

            ReleasePpo(&pmuns);
			}
		else
			{
            FRemScenCore(iscen + 1);
			return(fFalse);
			}
		}
	else
		{

		if (!Pscen()->FSetBkgd(ptag))
			{
			Bug("warning: set background failed.");
			return(fFalse);
			}
		}

    SetDirty();
	InvalViewsAndScb();

	return(fTrue);
}

/***************************************************************************
 *
 * Plays a movie.
 *
 * Parameters:
 *	None
 *
 * Returns:
 *  None.
 *
 **************************************************************************/
void MVIE::Play()
{
	AssertThis(0);

	if (FPlaying())
		{

		if (FStopPlaying())
			{
			//
			// We will stop it soon anyway.
			//
			return;
			}

        //
		// Kill playing timer
		//
		SetFStopPlaying(fTrue);

		if (_fPausing)
			{
			//
			// There is no clock timer, so call directly
			//
	        FCmdRender(pvNil);
			}

		}
	else
		{

		if (Pscen() == pvNil)
			{
			Pmcc()->PlayStopped();
			return;
			}

		// reset any outstanding sounds (from listener preview)
		_pmsq->StopAll();
		// flush any outstanding sound messages (from listener preview)
		_pmsq->FlushMsq();

		//
		// Check for if we need to rewind
		//
		if ((Iscen() + 1 == Cscen()) && (Pscen()->Nfrm() == Pscen()->NfrmLast()))
			{
			if (!FSwitchScen(0))
				{
				Pmcc()->PlayStopped();
				return;
				}
			if (!Pscen()->FGotoFrm(Pscen()->NfrmFirst()))
				{
				Pmcc()->PlayStopped();
				return;
				}
			}

        //
		// Start playing timer
		//
		_fOldSoundsEnabled = FSoundsEnabled();
		SetFSoundsEnabled(fTrue);
		_cnfrm = 0;
		_tsStart = TsCurrent();
		SetFStopPlaying(fFalse);
		_clok.Start(0);
        SetFPlaying(fTrue);
		vpcex->EnqueueCid(cidMviePlaying, pvNil, pvNil, fTrue);

		if (!_clok.FSetAlarm(0, this))
			{
			Pmcc()->PlayStopped();
			SetFPlaying(fFalse);
			_pmsq->PlayMsq();
			SetFSoundsEnabled(_fOldSoundsEnabled);
			return;
			}

		//
		// Check if we need to play the opening transition
		//
		_pmsq->SndOnLong();
		Pscen()->Enable(fscenPauses);
		Pscen()->SelectActr(pvNil);
		Pscen()->SelectTbox(pvNil);
		// Have to FReplayFrm *before* FStartPlaying because a camera view
		// change in FReplayFrm would wipe out prerendering (which FStartPlaying
		// initiates)
		if (!Pscen()->FReplayFrm(fscenPauses | fscenSounds | fscenActrs) ||
			!Pscen()->FStartPlaying())
			{
			Pmcc()->PlayStopped();
			SetFStopPlaying(fTrue);
			_pmsq->PlayMsq();
			SetFSoundsEnabled(_fOldSoundsEnabled);
			return;
			}

		Pmcc()->DisableAccel();

		if ((Iscen() == 0) &&
			(Pscen()->Nfrm() == Pscen()->NfrmFirst()))
			{
			_pmcc->UpdateScrollbars();
			InvalViews();
			vpappb->UpdateMarked();
			}

		Pscen()->PlayBkgdSnd();

		}

	// Play sound queue	
	if (FSoundsEnabled())
		{
		_pmsq->PlayMsq();
		}
	else
		{
		_pmsq->FlushMsq();
		}
	return;
}

/***************************************************************************
 *
 * Handle an alarm going off.
 *
 * This routine simply enqueues a command to render a frame.  We do it this
 * way because the clock pre-empts commands in the command queue.  If the
 * user mouse clicks, we need to process those clicks.  The best way to do
 * that is by doing rendering via the command queue.
 *
 * Parameters:
 *	pcmd - Pointer to the command to process.
 *
 * Returns:
 *  fTrue if it handled the command, else fFalse.
 *
***************************************************************************/
bool MVIE::FCmdAlarm(PCMD pcmd)
{
	AssertThis(0);
	AssertVarMem(pcmd);

	CMD cmd;

	if (FIdleSeen() || vpcex->PgobTracking() != pvNil)
		{

		SetFIdleSeen(fFalse);

		ClearPb(&cmd, size(CMD));
	
		cmd.pcmh = this;
		cmd.cid = cidRender;
		vpcex->EnqueueCmd(&cmd);
	
		}
	else
		{

		//
		// Check again, asap.
		//
		if (!_clok.FSetAlarm(0, this))
			{
			PMVU pmvu;
		
			//
			// Things are in a bad way.
			//
			Pmcc()->EnableAccel();
			SetFPlaying(fFalse);
			SetFStopPlaying(fFalse);
			SetFSoundsEnabled(_fOldSoundsEnabled);
			_clok.RemoveCmh(this);
			_fPausing = fFalse;
			_fScrolling = fFalse;
			_wit = witNil;
			pmvu = (PMVU)PddgGet(0);
			pmvu->PauseUntilClick(fFalse);
			Pscen()->Enable(fscenTboxes);
			Pscen()->Disable(fscenPauses);
	
			//
			// Update views and scroll bars
			//
			InvalViewsAndScb();
	
			//
			// Clean up anything else
			//
			Pscen()->StopPlaying();
			vpcex->EnqueueCid(cidMviePlaying, pvNil, pvNil, fFalse);
	
			//
			// Set sound queue state
			//
			_pmsq->SndOnShort();
	
			Pmcc()->PlayStopped();
			vpsndm->StopAll();

			}

		}

	return(fTrue);
}


/***************************************************************************
 *
 * Handle rendering a frame.
 *
 * This routine gets a little busy.  The basic premise is to render a
 * frame one frame ahead of the one currently displayed.  This means
 * that only actor stuff gets done first, then when the frame is to be
 * displayed, textboxes and sounds get started.  If there is a scrolling
 * text box, then we play nothing else until the scrolling is done, and
 * (finally) pauses in the frame.
 *
 * Parameters:
 *	pcmd - Pointer to the command to process.
 *
 * Returns:
 *  fTrue if it handled the command, else fFalse.
 *
***************************************************************************/
bool MVIE::FCmdRender(PCMD pcmd)
{
	AssertThis(0);
	AssertNilOrVarMem(pcmd);

	PMVU pmvu;
	PTBOX ptbox;
	long itbox;
	ulong tsCur = TsCurrent();

	pmvu = (PMVU)PddgGet(0);
    AssertPo(pmvu, 0);

	if (FStopPlaying())
		{
LStopPlaying:

		Pmcc()->EnableAccel();
        _clok.Stop();
		_clok.RemoveCmh(this);
		_fPausing = fFalse;
		_fScrolling = fFalse;
		_wit = witNil;
		SetFStopPlaying(fFalse);
		SetFPlaying(fFalse);
		SetFSoundsEnabled(_fOldSoundsEnabled);
		pmvu->PauseUntilClick(fFalse);
		if (Pscen() != pvNil)
			{
			Pscen()->Enable(fscenTboxes);
			Pscen()->Disable(fscenPauses);
			}
		else
			Assert(_iscen == ivNil, "Bogus scene state");

		//
		// Update views and scroll bars
		//
		InvalViewsAndScb();

		//
		// Clean up anything else
		//
		if (Pscen() != pvNil)
			Pscen()->StopPlaying();
		vpcex->EnqueueCid(cidMviePlaying, pvNil, pvNil, fFalse);

		//
		// Set sound queue state
		//
		_pmsq->SndOnShort();

		Pmcc()->PlayStopped();
		vpsndm->StopAll();

		// if we were fading, then restore sound volume
		if (_vlmOrg)
			{
			vpsndm->SetVlm(_vlmOrg);
			_vlmOrg = 0;
			}

		return(fTrue);
		}

	//	
	// if _vlmOrg is nonzero, then we are in the process of fading
	//
	if (_vlmOrg)
		{
		long vlm;

		// get the current volume
		vlm = vpsndm->VlmCur();

		// massage it
		vlm -= _vlmOrg/(kdtsVlmFade*4);	// kdtsVolFade seconds * 4 volume changes a second = total number deltas

		// if new volume is below 0, then we are done
		if (vlm <= 0)
			{
			SetFStopPlaying(fTrue);
			vpsndm->SetVlm(0);
			goto LStopPlaying;
			}

		// set the volume to new level
		vpsndm->SetVlm(vlm);

		if (!_clok.FSetAlarm(kdtimVlmFade, this))
			{
			goto LStopPlaying;
			}

		return fTrue;
		}

    if (_fScrolling)
		{
		//
		// Do next text box scroll.
		//
        _fScrolling = fFalse;

		for (itbox = 0; ; itbox++)
			{
            ptbox = Pscen()->PtboxFromItbox(itbox);
			AssertNilOrPo(ptbox, 0);
			if (ptbox == pvNil)	
				{
                break;
				}

            if (ptbox->FNeedToScroll())
				{
                ptbox->Scroll();
                _fScrolling = fTrue;
				}

			}

		if (_fScrolling && !_clok.FSetAlarm(kdtsScrolling, this))
			{
			_fScrolling = fFalse;
			goto LStopPlaying;
			}
		else if (_fScrolling)
			{
			return fTrue;
			}
		else
			{
			goto LCheckForPause;
			}

		}
	


	//
	// If we are not pausing, then start the stuff for this frame
	//
    if (!_fPausing)
		{

        //
    	// Now do all text boxes.
    	//
    	Pscen()->Enable(fscenTboxes);
    	Pscen()->Disable(fscenActrs);
        if (!Pscen()->FGotoFrm(Pscen()->Nfrm()))
    		{
    	    Pscen()->Enable(fscenActrs);
			goto LStopPlaying;
    		}
        Pscen()->Enable(fscenActrs);

		//
    	// Draw the previous frame
    	//
    	MarkViews();

    	//
    	// Update scroll bars
    	//
		_pmcc->UpdateScrollbars();

		//
		// Play outstanding sounds
		//
		if (FSoundsEnabled())
			{
			_pmsq->PlayMsq();
			}
		else
			{
			_pmsq->FlushMsq();
			}

		//
		// Flush to the screen
		//
    	vpappb->UpdateMarked();

		//
		// Check for any text box scrolling
		//
		//
		for (itbox = 0; ; itbox++)
			{
            ptbox = Pscen()->PtboxFromItbox(itbox);
			AssertNilOrPo(ptbox, 0);
			if (ptbox == pvNil)	
				{
                break;
				}

            if (ptbox->FNeedToScroll())
				{
				if (!_clok.FSetAlarm(kdtsScrolling, this))
					{
					goto LStopPlaying;
					}

                _fScrolling = fTrue;
				return(fTrue);
				}
			}

LCheckForPause:

		//
		// Check for a pause
		//
    	switch(_wit)
    		{
    	case witUntilClick:
    		_wit = witNil;
			_fPausing = fTrue;
    		pmvu->PauseUntilClick(fTrue);
    	    return(fTrue);
    	case witUntilSnd:
			if (_pmsq->FPlaying(fFalse))
				{
				_fPausing = fTrue;
				if (!_clok.FSetAlarm(0, this))
					{
					goto LStopPlaying;
					}
				return(fTrue);
				}
			
			_fPausing = fFalse;
			_wit = witNil;
			break;

    	case witForTime:
    		_wit = witNil;
			_fPausing = fTrue;
    		_clok.FSetAlarm(_dts, this);
    	    return(fTrue);
    	case witNil:
			_fPausing = fFalse;
    		break;
        default:
    		Bug("Bad Pause type");
    		}

		}
    else
		{
		if (_wit == witUntilSnd)
			{
			goto LCheckForPause;
			}
		else
			{
        	pmvu->PauseUntilClick(fFalse);
			_fPausing = fFalse;
			}
		}


	//
	// Advance everything to the next frame.
	//     Account for time spent between beginning of this routine and
	//     here.
	//
	if (!_clok.FSetAlarm(LwMax(0, kdtimFrame - LwMulDivAway((TsCurrent() - tsCur),
			kdtimSecond, kdtsSecond)),
			this, 0, fTrue))
		{
		goto LStopPlaying;
		}

	Pscen()->Disable(fscenTboxes);

	if (Pscen()->Nfrm() == Pscen()->NfrmLast())
		{

		if (Iscen() == (Cscen() - 1))
			{
			// since this is the last scene/last frame, we want to
			// fade out music, by setting _vlmOrg we turn off rendering and fade out
			// music until VlmCur is 0, at which point we go into stop state.
			
			if (!vpsndm->FPlayingAll()) 	// there are no sounds playing
				SetFStopPlaying(fTrue);		// there is nothing to fade
			else
				{
				_vlmOrg = vpsndm->VlmCur();		// get the current volume
				if ((0 == _vlmOrg))				// if there is volume to fade with
					SetFStopPlaying(fTrue);		// there is nothing to fade
				}
			return(fTrue);
			}

        _trans = Pscen()->Trans();

		if (FSwitchScen(Iscen() + 1))
			{
			AssertPo(Pscen(), 0);
			Pscen()->Disable(fscenTboxes);
			Pscen()->Enable(fscenPauses);
			if (!Pscen()->FReplayFrm(fscenPauses | fscenSounds | fscenActrs))
				{
				SetFStopPlaying(fTrue);
				return(fTrue);
				}

			Pscen()->PlayBkgdSnd();
			}
		else
			{
			SetFStopPlaying(fTrue);
			return(fTrue);
			}

		}
	else
		{

        if (!Pscen()->FGotoFrm( Pscen()->Nfrm() + 1))
			{
			SetFStopPlaying(fTrue);
			return(fTrue);
			}

		}

	Pbwld()->Render();
	_cnfrm++;

	return(fTrue);
}


/***************************************************************************
 *
 * This sets the costume of an actor.
 *
 * Parameters:
 *	ibprt - Id of the body part to set.
 *  ptag - Pointer to the tag of the costume, if fCustom != fTrue.
 *  cmid - The cmid of the costume, if fCustom == fTrue.
 *  fCustom - Is this a custom costume.
 *
 * Returns:
 *  fTrue if successful, else fFalse.
 *
 **************************************************************************/
bool MVIE::FCostumeActr(long ibprt, PTAG ptag, long cmid, bool fCustom)
{
	AssertThis(0);
	AssertPo(Pscen(), 0);

	ACTR *pactr;

	//
	// Get the current actor
	//
	pactr = Pscen()->PactrSelected();
	AssertPo(pactr, 0);

	if (!pactr->FSetCostume(ibprt, ptag, cmid, fCustom))
		{
		return(fFalse);
		}

    SetDirty();
	InvalViews();

	return(fTrue);
}



/***************************************************************************
 *
 * This inserts a pause into the scene right now.
 *
 * Parameters:
 *	wit - The type of pause to insert, or witNil to clear pauses.
 *  dts - Number of clock ticks to pause.
 *
 * Returns:
 *  fTrue if successful, else fFalse.
 *
 **************************************************************************/
bool MVIE::FPause(WIT wit, long dts)
{
	AssertThis(0);
	AssertPo(Pscen(), 0);

	if (!Pscen()->FPause(wit, dts))
		{
		return(fFalse);
		}

    SetDirty();
	return(fTrue);
}


#ifdef DEBUG
/******************************************************************************
	MarkMem
		Marks memory used by the CMVI
	
************************************************************ PETED ***********/
void CMVI::MarkMem(void)
{
	long iv, ivMac;
	MVIED mvied;
	SCEND scend;

	ivMac = pglmvied->IvMac();
	for (iv = 0; iv < ivMac; iv++)
		{
		pglmvied->Get(iv, &mvied);
		MarkMemObj(mvied.pcrf);
		}
	MarkMemObj(pglmvied);

	ivMac = pglscend->IvMac();
	for (iv = 0; iv < ivMac; iv++)
		{
		pglscend->Get(iv, &scend);
		MarkMemObj(scend.pmbmp);
		}
	MarkMemObj(pglscend);
}
#endif // DEBUG


/******************************************************************************
	FAddToCmvi
		Generates a GL of SCENDs that describes this movie.  A movie client
		that wishes to makes wholesale changes to a movie may get this GL,
		rearrange it, including inserting references to new movie files, and
		pass it back to the movie via FSetCmvi to modify the movie.
		Adds the movie to the GL of movie descriptors.

	Arguments:
		PCMVI pcmvi     --  the CMVI to add the movie to
		long iscendIns  --  the point at which to start inserting scenes

	Returns: fTrue if it was successful, fFalse otherwise
	
************************************************************ PETED ***********/
bool MVIE::FAddToCmvi(PCMVI pcmvi, long *piscendIns)
{
	AssertThis(0);
	AssertVarMem(pcmvi);
	AssertNilOrPo(pcmvi->pglscend, 0);

	long iscen = 0, iscenMac = Cscen(), imvied;
	SCEND scend;
	MVIED mvied;
	PCFL pcfl;

	scend.imvied = ivNil;

#ifdef BUG1929
	/* Ensure that _pcrfAutoSave points to the temp file, not the original movie file */
	if (!_FUseTempFile())
		goto LFail;
#endif //BUG1929

	if (!FAutoSave(pvNil, fFalse))
		goto LFail;

	if ((pcmvi->pglscend == pvNil) &&
			(pcmvi->pglscend = GL::PglNew(size(SCEND))) == pvNil)
		{
		goto LFail;
		}

	if ((pcmvi->pglmvied == pvNil) &&
			(pcmvi->pglmvied = GL::PglNew(size(MVIED))) == pvNil)
		{
		goto LFail;
		}

	mvied.pcrf = _pcrfAutoSave;
	mvied.cno = _cno;
	mvied.aridLim = _aridLim;
	if (!pcmvi->pglmvied->FAdd(&mvied, &imvied))
		goto LFail;
	scend.imvied = imvied;
	mvied.pcrf->AddRef();

	pcfl = mvied.pcrf->Pcfl();
	scend.fNuked = fFalse;

	for (iscen = 0; iscen < iscenMac; iscen++, (*piscendIns)++)
		{
		bool fSuccess;
		KID kid;

		/* Get CNO */
		AssertDo(pcfl->FGetKidChidCtg(kctgMvie, _cno, iscen, kctgScen, &kid),
			"Not enough scene chunks for movie");
		scend.cno = kid.cki.cno;
		scend.chid = iscen;
		scend.pmbmp = pvNil;

		/* Get PMBMP and TRANS from the scene */
		if (iscen != Iscen())
			{
			BLCK blck;

			if (!SCEN::FTransOnFile(mvied.pcrf, scend.cno, &scend.trans))
				goto LFail;

			AssertDo(pcfl->FGetKidChidCtg(
				kctgScen, scend.cno, 0, kctgThumbMbmp, &kid),
				"Scene doesn't have a thumbnail");
			if (!pcfl->FFind(kid.cki.ctg, kid.cki.cno, &blck))
				goto LFail;
			if ((scend.pmbmp = MBMP::PmbmpRead(&blck)) == pvNil)
				goto LFail;
			}
		else
			{
			scend.trans = Pscen()->Trans();
			if ((scend.pmbmp = Pscen()->PmbmpThumbnail()) == pvNil)
				goto LFail;
			scend.pmbmp->AddRef();
			}

		/* Add to the list */
		fSuccess = _FInsertScend(pcmvi->pglscend, *piscendIns, &scend);
		ReleasePpo(&scend.pmbmp);
		if (!fSuccess)
			goto LFail;
		}

	return fTrue;
LFail:
	while (iscen--)
		_DeleteScend(pcmvi->pglscend, --(*piscendIns));
	if (pcmvi->pglscend != pvNil && pcmvi->pglscend->IvMac() == 0)
		ReleasePpo(&pcmvi->pglscend);
	if (scend.imvied != ivNil)
		{
		ReleasePpo(&mvied.pcrf);
		pcmvi->pglmvied->Delete(scend.imvied);
		}
	if (pcmvi->pglmvied != pvNil && pcmvi->pglmvied->IvMac() == 0)
		ReleasePpo(&pcmvi->pglmvied);
	return fFalse;
}


/******************************************************************************
	FSetCmvi
		Rebuilds the movie based on the given CMVI.  Any scenes
		marked for deletion are disowned by their MVIE chunk.  Any scenes that
		refer to a movie file other than this MVIE's auto save file are
		copied into this MVIE's auto save file.  SCEN chunks are given new
		CHIDs reflecting their new position within the movie.  The non-nuked
		scenes must appear in the GL in the order that they appear in the
		movie; other than that, there is no restriction on the order of the
		scenes (ie, nuked scenes can appear anywhere in the GL, even though
		currently the only client of this API keeps the nuked scenes at the
		end).

	Arguments:
		PCMVI pcmvi -- the CMVI that describes the new movie structure
	
	Returns: fTrue if it could accomplish all of the above, fFalse otherwise
	
************************************************************ PETED ***********/
bool MVIE::FSetCmvi(PCMVI pcmvi)
{
	AssertThis(0);
	AssertVarMem(pcmvi);
	AssertPo(pcmvi->pglmvied, 0);
	AssertPo(pcmvi->pglscend, 0);

	bool fRet = fFalse;
	long iscend, iscendMac = pcmvi->pglscend->IvMac();
	long iscenOld = Iscen();
	long imvied, imviedMac = pcmvi->pglmvied->IvMac();
	long aridMin = 0;
	CHID chidScen = 0;
	PCFL pcfl = _pcrfAutoSave->Pcfl();
	PCRF pcrf = _pcrfAutoSave;
	PGL pglmviedNew;

	pglmviedNew = pcmvi->pglmvied->PglDup();
	if (pglmviedNew == pvNil)
		goto LFail;

	/* Copy all the external movie chunks into this movie */
	for (imvied = 0; imvied < imviedMac; imvied++)
		{
		MVIED mvied;

		pglmviedNew->Get(imvied, &mvied);

		if (imvied > 0)
			{
			if (!mvied.pcrf->Pcfl()->FClone(kctgMvie, mvied.cno, pcfl, &mvied.cno))
				goto LFail;
			if (!_FAddMvieToRollCall(mvied.cno, aridMin))
				goto LFail;
			mvied.pcrf = pcrf;
			mvied.pcrf->AddRef();
			pglmviedNew->Put(imvied, &mvied);
			}
		else
			{
			Assert(mvied.pcrf == pcrf, "Invalid GL of MVIEDs");
			Assert(mvied.cno == _cno, "Invalid GL of MVIEDs");
			}
		aridMin += mvied.aridLim;
		}

	for (iscend = 0; iscend < iscendMac; iscend++)
		{
		CNO cnoScen = cnoNil;
		SCEND scend;
		MVIED mvied;

		pcmvi->pglscend->Get(iscend, &scend);
		pglmviedNew->Get(scend.imvied, &mvied);

		/* Was this scene imported? */
		if (scend.imvied > 0)
			{
			KID kid;

			if (!pcfl->FGetKidChidCtg(
					kctgMvie, mvied.cno, scend.chid, kctgScen, &kid))
				{
				goto LFail;
				}
			cnoScen = kid.cki.cno;

			/* If so, only bother keeping it if the user didn't delete it */
			if (!scend.fNuked)
				{
				PSCEN pscen;

				if (!pcfl->FAdoptChild(kctgMvie, _cno, kctgScen, cnoScen, chidScen++))
					goto LFail;
				if (!_FAdoptMsndInMvie(pcfl, cnoScen))
					goto LFail;
				if ((pscen = SCEN::PscenRead(this, pcrf, cnoScen)) == pvNil ||
						!pscen->FPlayStartEvents(fTrue) ||
						!pscen->FAddActrsToRollCall())
					{
					PushErc(ercSocNoImportRollCall);
					}

				Pmcc()->EnableActorTools();
				Pmcc()->EnableTboxTools();
				ReleasePpo(&pscen);
				}
			}
		else
			{
			if (scend.fNuked)
				{
				PSCEN pscen;

				if (scend.chid != (CHID)iscenOld)
					{
					pscen = SCEN::PscenRead(this, pcrf, scend.cno);
					if (pscen == pvNil || !pscen->FPlayStartEvents(fTrue))
						PushErc(ercSocNoNukeRollCall);
					}
				else
					{
					pscen = _pscenOpen;
					pscen->AddRef();
					}

				if (pscen != pvNil)
					{
					pscen->RemActrsFromRollCall(fTrue);
					ReleasePpo(&pscen);
					}
				pcfl->DeleteChild(
					kctgMvie, _cno, kctgScen, scend.cno, scend.chid);
				}
			else
				{
				/* Set the CHID to be the current scene number */
				cnoScen = scend.cno;
				pcfl->ChangeChid(kctgMvie, _cno, kctgScen, scend.cno,
					scend.chid, chidScen++);
				}
			}

		/* If we didn't delete the scene, go ahead and update its transition */
		if (scend.chid != (CHID)iscenOld || scend.imvied != 0)
			{
			if (!scend.fNuked)
				{
				Assert(mvied.pcrf == pcrf, "Scene's MVIE didn't get copied");
				Assert(cnoScen != cnoNil, "Didn't set the cnoScen");
				if (!SCEN::FSetTransOnFile(pcrf, cnoScen, scend.trans))
					goto LFail;
				}
			}
		else
			{
			if (scend.fNuked)
				{
				/* Basically, do an _FCloseCurrentScene w/out the autosave */
				SCEN::Close(&_pscenOpen);
				_iscen = ivNil;
				}
			else
				{
				/* If this is the scene that *used* to be Iscen(), change the
					transition in memory rather than on file */
				Pscen()->SetTransitionCore(scend.trans);
				_iscen = iscend;
				}
			}
		}

	_cscen = chidScen;
	_aridLim = aridMin;
	SetDirty();
	if (_cscen == 0)
		{
		Assert(_iscen == ivNil, 0);
		_pmcc->SceneNuked();
		}
	InvalViewsAndScb();
	Pmcc()->UpdateRollCall();

#ifdef DEBUG
	{
	long ckid = pcfl->Ckid(kctgMvie, _cno);
	KID kid;
	CHID chidLast = chidNil;

	for (long ikid = 0; ikid < ckid; ikid++)
		{
		if (pcfl->FGetKid(kctgMvie, _cno, ikid, &kid))
			{
			if (kid.cki.ctg == kctgScen)
				{
				Assert(chidLast == chidNil || kid.chid > chidLast, "Found duplicate CHID in scene children");
				chidLast = kid.chid;
				}
			}
		else
			{
			Bug("Can't guarantee validity of MVIE's SCEN children");
			break;
			}
		}
	}
#endif /* DEBUG */

	fRet = fTrue;
LFail:
	if (pglmviedNew != pvNil)
		{
		/* Remove any copied movies; leave the first MVIED alone */
		while (imvied-- > 1)
			{
			MVIED mvied;

			pglmviedNew->Get(imvied, &mvied);
			Assert(mvied.pcrf == pcrf, "Invalid MVIED during cleanup");
			pcfl->Delete(kctgMvie, mvied.cno);
			ReleasePpo(&mvied.pcrf);
			}
		ReleasePpo(&pglmviedNew);
		}
	return fRet;
}


/******************************************************************************
	_FAddMvieToRollCall
		Updates roll call (including remapping arids for the actors found in
		the new movie) for a given MVIE that's just been copied into this
		movie's file.
	
	Arguments:
		CNO cno       -- the CNO of the copied movie
		long aridMin  -- the new base arid for this movie's actors
	
	Returns: fTrue if it succeeds, fFalse otherwise
	
************************************************************ PETED ***********/
bool MVIE::_FAddMvieToRollCall(CNO cno, long aridMin)
{
	AssertThis(0);

	long imactr, imactrMac, icnoMac = 0;
	PCFL pcfl = _pcrfAutoSave->Pcfl();
	PGST pgstmactr = pvNil;

	/* Update the roll call GST */
	if (!FReadRollCall(_pcrfAutoSave, cno, &pgstmactr))
		{
		pgstmactr = pvNil;
		goto LFail;
		}
	imactrMac = pgstmactr->IvMac();
	for (imactr = 0; imactr < imactrMac; imactr++)
		{
		STN stn;
		MACTR mactr;

		pgstmactr->GetStn(imactr, &stn);
		pgstmactr->GetExtra(imactr, &mactr);

		mactr.arid += aridMin;
		mactr.cactRef = 0;
		if (!_pgstmactr->FAddStn(&stn, &mactr))
			goto LFail;
		}

	/* Remap all the arids on the file */
	if (aridMin > 0)
		{
		ulong grfcge, grfcgeIn = fcgeNil;
		PGL pglcno;
		CKI ckiParLast = { ctgNil, cnoNil }, ckiPar;
		KID kid;
		CGE cge;

		if ((pglcno = GL::PglNew(size(CNO))) == pvNil)
			goto LFail;
		cge.Init(pcfl, kctgMvie, cno);
		while (cge.FNextKid(&kid, &ckiPar, &grfcge, fcgeNil))
			{
			if (grfcge & fcgePre)
				{

				/* If we've found an ACTR chunk, remap its arid */
				if (kid.cki.ctg == kctgActr)
					{
					long icno;
					CNO cnoActr;

					/* Only do a given chunk once */
					Assert(icnoMac == pglcno->IvMac(), "icnoMac isn't up-to-date");
					for (icno = 0; icno < icnoMac; icno++)
						{
						pglcno->Get(icno, &cnoActr);
						if (kid.cki.cno == cnoActr)
							break;
						}
					if (icno < icnoMac)
						continue;
					if (!pglcno->FAdd(&kid.cki.cno))
						goto LFail1;

					/* Change the arid */
					if (!ACTR::FAdjustAridOnFile(pcfl, kid.cki.cno, aridMin))
						{
					/* Don't bother trying to fix the arids on file; the caller
						should be deleting the copied MVIE chunk anyway */
LFail1:
						ReleasePpo(&pglcno);
						goto LFail;
						}
					icnoMac++;

					/* Once we're at an ACTR chunk, set up so that we don't
						enumerate down again until returning to our parent's
						next sibling */
					ckiParLast = ckiPar;
					grfcgeIn = fcgeSkipToSib;
					}
				}
			else if (grfcge & fcgePost && grfcgeIn & fcgeSkipToSib)
				{
				if (ckiParLast.ctg != ckiPar.ctg || ckiParLast.cno != ckiPar.cno)
					grfcgeIn = fcgeNil;
				}
			}
		ReleasePpo(&pglcno);
		}

	ReleasePpo(&pgstmactr);

	return fTrue;
LFail:
	/* NOTE: I could use more variables and make the loops below faster,
		but this is a failure case so I'm not very concerned about
		performance here */
	if (pgstmactr != pvNil)
		{
		MACTR mactr;

		/* Remove added entries to the movie's roll call */
		imactrMac = _pgstmactr->IvMac();
		while (imactr--)
			{
			_pgstmactr->GetExtra(--imactrMac, &mactr);
			vptagm->CloseTag(&mactr.tagTmpl);
			_pgstmactr->Delete(imactrMac);
			pgstmactr->Delete(imactr);
			}

		/* Close any other uncopied tags */
		imactrMac = pgstmactr->IvMac();
		while (imactrMac--)
			{
			pgstmactr->GetExtra(imactrMac, &mactr);
			vptagm->CloseTag(&mactr.tagTmpl);
			}
		ReleasePpo(&pgstmactr);
		}
	return fFalse;
}

/******************************************************************************
	EmptyCmvi
		Frees up the memory used by the CMVI.  For each scene in the
		GL of SCENDs, releases memory that the SCEND referred to.  Likewise
		for each MVIED in the GL of MVIEDs.
	
	Arguments:
		PCMVI pcmvi -- the CMVI to empty
	
	Returns: Sets the client's pointer to pvNil when finished
	
************************************************************ PETED ***********/
void CMVI::Empty(void)
{
	AssertPo(pglscend, 0);
	AssertPo(pglmvied, 0);

	PGL pgl;


	if ((pgl = pglscend) != pvNil)
		{
		long iscend = pgl->IvMac();

		while (iscend-- > 0)
			{
			SCEND scend;
	
			pgl->Get(iscend, &scend);
			ReleasePpo(&scend.pmbmp);
			}
		ReleasePpo(&pgl);
		pglscend = pvNil;
		}

	if ((pgl = pglmvied) != pvNil)
		{
		long imvied = pgl->IvMac();

		while (imvied-- > 0)
			{
			MVIED mvied;

			pgl->Get(imvied, &mvied);
			ReleasePpo(&mvied.pcrf);
			}
		ReleasePpo(&pgl);
		pglmvied = pvNil;
		}
}


/******************************************************************************
	_FInsertScend
		Inserts the given SCEND into a GL of SCENDs that was created by this
		movie.
	
	Arguments:
		PGL pglscend  -- the GL of SCENDs to insert into
		long iscend   -- the position at which to insert this SCEND
		PSCEND pscend -- the SCEND to insert
	
	Returns: fTrue if successful, fFalse otherwise
	
************************************************************ PETED ***********/
bool MVIE::_FInsertScend(PGL pglscend, long iscend, PSCEND pscend)
{
	AssertPo(pglscend, 0);
	AssertPo(pscend->pmbmp, 0);

	if (!pglscend->FInsert(iscend, pscend))
		return fFalse;
	pscend->pmbmp->AddRef();
	return fTrue;
}


/******************************************************************************
	_DeleteScend
		Deletes the given SCEND from a GL of SCENDs that was created by this
		movie.
	
	Arguments:
		PGL pglscend -- the GL of SCENDs to delete from
		long iscend  -- which SCEND to delete
	
************************************************************ PETED ***********/
void MVIE::_DeleteScend(PGL pglscend, long iscend)
{
	AssertPo(pglscend, 0);
	AssertIn(iscend, 0, pglscend->IvMac());

	SCEND scend;

	pglscend->Get(iscend, &scend);
	AssertPo(scend.pmbmp, 0);
	pglscend->Delete(iscend);
	ReleasePpo(&scend.pmbmp);
}


/***************************************************************************
 *
 * This sets the transition type for the current scene.
 *
 * Parameters:
 *	trans - The transition type.
 *
 * Returns:
 *  fTrue if successful, else fFalse.
 *
 **************************************************************************/
bool MVIE::FSetTransition(TRANS trans)
{
	AssertThis(0);
	AssertIn(trans, 0, transLim);
	AssertPo(Pscen(), 0);

	if (!Pscen()->FSetTransition(trans))
		{
		return(fFalse);
		}

    SetDirty();
	return(fTrue);
}



/***************************************************************************
 *
 * This pastes an actor into the movie.
 *
 * Parameters:
 *	pactr - A pointer to the actor to paste.
 *
 * Returns:
 *  fTrue if successful, else fFalse.
 *
 **************************************************************************/
bool MVIE::FPasteActr(PACTR pactr)
{
	AssertThis(0);
	AssertPo(pactr, 0);
	AssertPo(Pscen(), 0);

	PMVU pmvu;

	pmvu = (PMVU)PddgGet(0);
	if (pmvu == pvNil)
		{
		return(fFalse);
		}
    AssertPo(pmvu, 0);

	//
	// Paste this actor in and select it.
	//
	if (!Pscen()->FPasteActr(pactr))
		{
		return(fFalse);
		}

	Pscen()->SelectActr(pactr);
	
	//
 	// Positioning a pasted actor must translate all subroutes
	//
 	pmvu->StartPlaceActor(fTrue);

    SetDirty();
	InvalViewsAndScb();

	return(fTrue);
}

/***************************************************************************
 *
 * This pastes an actor path onto the selected actor.
 *
 * Parameters:
 *	pactr - A pointer to the actor to paste from.
 *
 * Returns:
 *  fTrue if successful, else fFalse.
 *
 **************************************************************************/
bool MVIE::FPasteActrPath(PACTR pactr)
{
	AssertThis(0);
	AssertPo(pactr, 0);
	AssertPo(Pscen(), 0);
	AssertNilOrPo(Pscen()->PactrSelected(), 0);

	PACTR pactrDup;

	// REVIEW SeanSe(seanse): This is wrong.  Move the undo stuff into
	//     ACTR::FPasteRte() and make ACTR::FPasteRte into FPasteRteCore.
	if (Pscen()->PactrSelected() == pvNil)
		{
        PushErc(ercSocNoActrSelected);
		return(fFalse);
		}

	if (!Pscen()->PactrSelected()->FDup(&pactrDup))
		{
		return(fFalse);
		}	

	//
	// Paste this actor in and select it.
	//
	if (!Pscen()->PactrSelected()->FPasteRte(pactr))
		{
		ReleasePpo(&pactrDup);
		return(fFalse);
		}

	if (!Pscen()->PactrSelected()->FCreateUndo(pactrDup))
		{
		Pscen()->PactrSelected()->Restore(pactrDup);
		ReleasePpo(&pactrDup);
		return(fFalse);
		}

    SetDirty();
	InvalViewsAndScb();
	ReleasePpo(&pactrDup);

	return(fTrue);
}



/***************************************************************************
 *
 * This invalidates all the views on the movie.
 *
 * Parameters:
 *	None.
 *
 * Returns:
 *  None.
 *
 **************************************************************************/
void MVIE::InvalViews(void)
{
	AssertThis(0);

	long ipddg;
	PDDG pddg;

	for (ipddg = 0; pvNil != (pddg = PddgGet(ipddg)); ipddg++)
		{
		pddg->InvalRc(pvNil, pddg == PddgActive() ? kginMark : kginSysInval);
	    }
}



/***************************************************************************
 *
 * This invalidates scrollbars and all the views on the movie.
 *
 * Parameters:
 *	None.
 *
 * Returns:
 *  None.
 *
 **************************************************************************/
void MVIE::InvalViewsAndScb(void)
{
	AssertThis(0);

	_pmcc->UpdateScrollbars();
	InvalViews();
}

/***************************************************************************
 *
 * This updates all the views on the movie, updates will happen
 * asap.
 *
 * Parameters:
 *	None.
 *
 * Returns:
 *  None.
 *
 **************************************************************************/
void MVIE::MarkViews(void)
{
	AssertThis(0);

	long ipddg;
	PDDG pddg;

	//
	// Need this call in order to mark correctly the changed regions.
	//
	Pbwld()->Render();

#ifdef DEBUG
	if (FWriteBmps())
		{
		FNI fni;
		STN stn;

		if (stn.FFormatSz(PszLit("cel%04d.dib"), _lwBmp++))
			{
			if (fni.FBuildFromPath(&stn))
				{
				if (!Pbwld()->FWriteBmp(&fni))
					SetFWriteBmps(fFalse);
				}
			}
		}
#endif // DEBUG

	for (ipddg = 0; pvNil != (pddg = PddgGet(ipddg)); ipddg++)
		{
		Pbwld()->MarkRenderedRegn(pddg, 0, 0);
	    }
}


/***************************************************************************
 *
 * This returns the current name of the movie.
 *
 * Parameters:
 *	pstnTitle - An stn to copy the name into.
 *
 * Returns:
 *  None.
 *
 **************************************************************************/
void MVIE::GetName(PSTN pstnTitle)
{
	AssertThis(0);
	AssertPo(pstnTitle, 0);

	*pstnTitle = _stnTitle;
}


/******************************************************************************
	ResetTitle
		Resets the movie title to whatever it's normal default would be (either
		from the filename or from the MCC string table).
************************************************************ PETED ***********/
void MVIE::ResetTitle(void)
{
	AssertThis(0);

	FNI fni;

	_stnTitle.SetNil();
	_SetTitle(FGetFni(&fni) ? &fni : pvNil);
}


/***************************************************************************
 *
 * Updates the external action menu.
 *
 * Parameters:
 *	None.
 *
 * Returns:
 *  None.
 *
 **************************************************************************/
void MVIE::BuildActionMenu()
{
	AssertThis(0);
	long arid = aridNil;

	if (pvNil != _pscenOpen && pvNil != _pscenOpen->PactrSelected())
		{
		arid = _pscenOpen->PactrSelected()->Arid();
		}
	_pmcc->ActorSelected(arid);
	_pmcc->UpdateAction();
}


const long kdtsTrans = 4 * kdtsSecond;

/***************************************************************************
 *
 * Does a transition.  Note that the rectangles should be the exact size
 * of the rendered area in order to get pushing, dissolve, etc to look perfect.
 *
 * Note: A cool fun number fact.  If you take a number from 1 - (klwPrime - 1),
 * multiply by klwPrimeRoot and modulo the result by klwPrime, you will get
 * a sequence of numbers which hits every number 1->(klwPrime-1) w/o repeating
 * until every number is hit.  This fact is used to create the dissolve effect.
 *
 * Parameters:
 *	pgnvDst - The destination GNV.
 *	pgnvSrc - The source GNV.
 *	prcDst - The clipping rectangle in the destination.
 *	prcSrc - The clipping rectangle in the source.
 *
 * Returns:
 *  None.
 *
 **************************************************************************/
void MVIE::DoTrans(PGNV pgnvDst, PGNV pgnvSrc, RC *prcDst, RC *prcSrc)
{
	AssertThis(0);
	AssertPo(pgnvDst, 0);
	AssertPo(pgnvSrc, 0);
	AssertVarMem(prcDst);
	AssertVarMem(prcSrc);

	PGL pglclrSystem = pvNil;
	PGL pglclrBkgd = pvNil;
	long iclrMin;

	pglclrSystem = GPT::PglclrGetPalette();
	if (Pscen() == pvNil || !Pscen()->Pbkgd()->FGetPalette(&pglclrBkgd, &iclrMin))
		{
		pglclrBkgd = pvNil;
		}
	if (pvNil != pglclrSystem && pvNil != pglclrBkgd)
		{
		Assert(pglclrBkgd->IvMac() + iclrMin <= pglclrSystem->IvMac(),
			"Background palette too large");
		CopyPb(pglclrBkgd->QvGet(0), pglclrSystem->QvGet(iclrMin),
			LwMul(size(CLR), pglclrBkgd->IvMac()));
		}

	switch (_trans)
		{
	case transBlack:
		pgnvDst->FillRc(prcDst, kacrBlack);
		break;
		
	case transFadeToBlack:
		pgnvDst->Dissolve(0, 0, kacrBlack, pgnvSrc, prcSrc, prcDst, kdtsTrans / 2, pglclrSystem);
		break;

	case transFadeToWhite:
		pgnvDst->Dissolve(0, 0, kacrWhite, pgnvSrc, prcSrc, prcDst, kdtsTrans / 2, pglclrSystem);
		break;

	case transDissolve:
		pgnvDst->Dissolve(0, 0, kacrClear, pgnvSrc, prcSrc, prcDst, kdtsTrans, pglclrSystem);
 		break;

	case transCut:
		pgnvDst->FillRc(prcDst, kacrBlack);
		GPT::SetActiveColors(pglclrSystem, fpalIdentity);
		pgnvDst->CopyPixels(pgnvSrc, prcSrc, prcDst);
		break;

	default:
		Bug("bad trans");
		break;
		}

	ReleasePpo(&pglclrSystem);
	ReleasePpo(&pglclrBkgd);

	_trans = transNil;
}

/***************************************************************************
 *
 * Used to query if an actor or tbox exists.
 *
 * Parameters:
 *	lwType - 1 if searching for an actor, else 0.
 *  lwId - Arid or Itbox to search for.
 *
 * Returns:
 *  1 if exists, else 0.
 *
 **************************************************************************/
long MVIE::LwQueryExists(long lwType, long lwId)
{
	AssertThis(0);
	AssertIn(lwType, 0, 2);

	if (Pscen() == pvNil)
		{
		return(0);
		}
    AssertPo(Pscen(), 0);

	if (lwType == 1)
		{
		return(Pscen()->PactrFromArid(lwId) != pvNil ? 1 : 0);
		}
	
	return(Pscen()->PtboxFromItbox(lwId) != pvNil ? 1 : 0);
}


/***************************************************************************
 *
 * Used to query where an actor or tbox exists.
 *
 * Parameters:
 *	lwType - 1 if searching for an actor, else 0.
 *  lwId - Arid or Itbox to search for.
 *
 * Returns:
 *  -1 if nonexistent or non-visible, else x in high word, y in low word.
 *
 **************************************************************************/
long MVIE::LwQueryLocation(long lwType, long lwId)
{
	AssertThis(0);
	AssertIn(lwType, 0, 2);

	PACTR pactr;
	PTBOX ptbox;
	long xp, yp;
	RC rc;

	if (Pscen() == pvNil)
		{
		return(-1);
		}
    AssertPo(Pscen(), 0);

	if (lwType == 1)
		{
		RC rcBounds;
		long cactGuessPt = 0;
		RND rnd;
		long ibset;

		pactr = Pscen()->PactrFromArid(lwId);
		if (pactr == pvNil)
			{
			return(-1);
			}

        AssertPo(pactr, 0);
		if (!pactr->FIsInView())
			{
			return(-1);
			}

        pactr->GetCenter(&xp, &yp);
		pactr->GetRcBounds(&rcBounds);
		// The center of the actor may not be a selectable point (as in
		// an arched spletter), so try some random points if the center
		// point doesn't select the actor.
		while (pactr != Pscen()->PactrFromPt(xp, yp, &ibset) && cactGuessPt < 1000)
			{
			cactGuessPt++;
			xp = rnd.LwNext(rcBounds.Dxp() + rcBounds.xpLeft);
			yp = rnd.LwNext(rcBounds.Dyp() + rcBounds.ypTop);
			}
		if (cactGuessPt == 1000)
			{
			xp = -1;
			yp = -1;
			}
		return((xp << 16) | yp);
		}
	
	ptbox = Pscen()->PtboxFromItbox(lwId);
	if (ptbox == pvNil)
		{
		return(-1);
		}

    AssertPo(ptbox, 0);
	if (!ptbox->FIsVisible())
		{
		return(-1);
		}

    ptbox->GetRc(&rc);
	return((rc.xpLeft << 16) | rc.ypTop);
}

/***************************************************************************
 *
 * Used to set the position within the movie.
 *
 * Parameters:
 *	lwScene - Scene to go to.
 *  lwFrame - Frame to go to.
 *
 * Returns:
 *  0 if successful, else -1.
 *
 **************************************************************************/
long MVIE::LwSetMoviePos(long lwScene, long lwFrame)
{
	AssertThis(0);

	if (!FSwitchScen(lwScene))
		{
		return(-1);
		}

    if (!Pscen()->FGotoFrm(lwFrame))
		{
        return(-1);
		}

	InvalViewsAndScb();

	return(0);
}

/***************************************************************************
 *
 * Unused user sounds in this movie?
 *
 * Parms:
 *     bool pfHaveValid -- bool to take whether any of the unused sounds were
 *                         actually usable
 **************************************************************************/

bool MVIE::FUnusedSndsUser(bool *pfHaveValid)
{
	AssertThis(0);
	AssertNilOrVarMem(pfHaveValid);

	bool fUnused = fFalse;
	long icki, ccki;
	PCFL pcfl;

	if (pfHaveValid != pvNil)
		*pfHaveValid = fFalse;

	if (pvNil == _pcrfAutoSave)
		return fFalse;

	pcfl = _pcrfAutoSave->Pcfl();
	ccki = pcfl->CckiCtg(kctgMsnd);
	for (icki = 0; icki < ccki; icki++)
		{
		CKI cki;
		KID kid;

		AssertDo(pcfl->FGetCkiCtg(kctgMsnd, icki, &cki), "Should never fail");
		Assert(_FIsChild(pcfl, cki.ctg, cki.cno), "Not a child of MVIE chunk");
		if (pcfl->CckiRef(cki.ctg, cki.cno) < 2)
			{
			fUnused = fTrue;
			if (pfHaveValid != pvNil)
				{
				if (pcfl->FGetKidChid(cki.ctg, cki.cno, kchidSnd, &kid))
					{
					*pfHaveValid = fTrue;
					break;
					}
				}
			else
				break;
			}
		}
	return fUnused;
}



/***************************************************************************
 *
 * Used to set the title of the movie, based on a file name.
 *
 * Parameters:
 *  pfni - File name.
 *
 * Returns:
 *  None.
 *
 **************************************************************************/
void MVIE::_SetTitle(PFNI pfni)
{
	AssertThis(0);

	achar *pch;

	if (pfni == pvNil)
		{
		if (_stnTitle.Cch() == 0)
			{
			Pmcc()->GetStn(idsEngineDefaultTitle, &_stnTitle);
			Pmcc()->UpdateTitle(&_stnTitle);
			}
		return;
		}

	pfni->GetLeaf(&_stnTitle);

	for (pch = _stnTitle.Psz() + _stnTitle.Cch(); (pch > _stnTitle.Psz()) && (*pch != ChLit('.')); pch--)
		{
		}

	if (*pch == ChLit('.'))
		{
		_stnTitle.Delete(pch - _stnTitle.Psz());
		}

	if (_stnTitle.Cch() == 0)
		{
        Pmcc()->GetStn(idsEngineDefaultTitle, &_stnTitle);
		}

	Pmcc()->UpdateTitle(&_stnTitle);
}

//
//
//
//  BEGIN MVU GOODIES
//
//
//

BEGIN_CMD_MAP(MVU, DDG)
	ON_CID_GEN(cidCopyRoute, &MVU::FCmdClip, pvNil)
	ON_CID_GEN(cidCutTool, &MVU::FCmdClip, pvNil)
	ON_CID_GEN(cidShiftCut, &MVU::FCmdClip, pvNil)
	ON_CID_GEN(cidCopyTool, &MVU::FCmdClip, pvNil)
	ON_CID_GEN(cidShiftCopy, &MVU::FCmdClip, pvNil)
	ON_CID_GEN(cidPasteTool, &MVU::FCmdClip, pvNil)
	ON_CID_GEN(cidClose, pvNil, pvNil)
	ON_CID_GEN(cidSave, &MVU::FCmdSave, pvNil)
	ON_CID_GEN(cidSaveAs, &MVU::FCmdSave, pvNil)
	ON_CID_GEN(cidSaveCopy, &MVU::FCmdSave, pvNil)
	ON_CID_GEN(cidIdle, &MVU::FCmdIdle, pvNil)
	ON_CID_GEN(cidRollOff, &MVU::FCmdRollOff, pvNil)
END_CMD_MAP_NIL()

RTCLASS(MVU)

/****************************************************
 *
 * Destructor for movie view objects
 *
 ****************************************************/
MVU::~MVU(void)
{
	if (_tagTool.sid != ksidInvalid)
		TAGM::CloseTag(&_tagTool);
}

/***************************************************************************
 *
 * Create a new mvu.
 *
 * Parameters:
 *	pmvie - Pointer to the creating movie.
 *  pgcb - Pointer to the creation block describing placement, etc.
 *	dxp - Width of the rendered area.
 *	dyp - Height of the rendered area.
 *
 * Returns:
 *  A pointer to the view, otw pvNil on failure
 *
***************************************************************************/
MVU *MVU::PmvuNew(PMVIE pmvie, PGCB pgcb, long dxp, long dyp)
{
	AssertPo(pmvie, 0);
	AssertVarMem(pgcb);

	MVU *pmvu;
	BRS rgr[3][3] = {{rOne, rZero, rZero}, {rZero, rZero, rOne},
		{rZero, -rOne, rZero}};

	//
	// Create the new view
	//
	if ((pmvu = NewObj MVU(pmvie, pgcb)) == pvNil)
		return pvNil;

    //
	// Init it
	//
    if (!pmvu->_FInit())
		{
        ReleasePpo(&pmvu);
		return(pvNil);
		}

    CopyPb(rgr, pmvu->_rgrAxis, size(rgr));
	pmvu->_fRecordDefault = fTrue;
	pmvu->_fRespectGround = fFalse;
	pmvu->_dxp = dxp;
	pmvu->_dyp = dyp;
	pmvu->_tool = toolCompose;
	pmvu->_tagTool.sid = ksidInvalid;

	//
	// Make this the active view
	//
	pmvu->Activate(fTrue);
	return pmvu;
}



/***************************************************************************
 *
 * Set the tool type
 *
 * Parameters:
 *	tool - The new tool to use.
 *
 * Returns:
 *  None.
 *
***************************************************************************/
void MVU::SetTool(long tool)
{
	AssertThis(0);
	AssertPo(Pmvie(), 0);
	AssertNilOrPo(Pmvie()->Pscen(), 0);

	long lwMode; // -1 = Textbox mode, 0 = either mode, 1 = Actor mode
	PTBOX ptbox = pvNil;
	PACTR pactr = pvNil;

	if (Pmvie()->Pscen() != pvNil)
		{

		AssertNilOrPo(Pmvie()->Pscen()->PactrSelected(), 0);
		AssertNilOrPo(Pmvie()->Pscen()->PtboxSelected(), 0);

		pactr = Pmvie()->Pscen()->PactrSelected();
		ptbox = Pmvie()->Pscen()->PtboxSelected();

		if (pactr != pvNil)
			{
			_ptmplTool = pactr->Ptmpl();
			AssertPo(_ptmplTool, 0);
			}

		}
    else
		{
        _ptmplTool = pvNil;
		}

	//
	// Get old tool type
	//
	switch (_tool)
		{
	case toolSoonerLater:

		lwMode = 1;
		if (tool != toolSoonerLater)
			{
			Pmvie()->Pmcc()->EndSoonerLater();

			if ((pactr != pvNil) && pactr->FTimeFrozen())
				{
				pactr->SetTimeFreeze(fFalse);
				pactr->Hilite();
				Pmvie()->InvalViewsAndScb();
				}

			}
		break;

	case toolActorNuke:
	case toolCopyObject:
	case toolCopyRte:
	case toolCutObject:
	case toolPasteObject:
		lwMode = 0;
		break;

	case toolSceneNuke:
	case toolDefault:
	case toolSounder:
	case toolLooper:
	case toolMatcher:
	case toolListener:
	case toolSceneChop:
	case toolSceneChopBack:
	case toolAction:
	case toolActorSelect:
    case toolPlace:
	case toolCompose:
	case toolRecordSameAction:
	case toolRotateX:
	case toolRotateY:
	case toolRotateZ:
	case toolCostumeCmid:
	case toolSquashStretch:
	case toolResize:
	case toolNormalizeRot:
	case toolNormalizeSize:
	case toolActorEasel:

		lwMode = 1;
		break;

	case toolTboxMove:
	case toolTboxUpDown:
	case toolTboxLeftRight:
	case toolTboxFalling:
	case toolTboxRising:
	case toolTboxPaintText:
	case toolTboxFillBkgd:
	case toolTboxStory:
	case toolTboxCredit:
	case toolTboxFont:
	case toolTboxStyle:
	case toolTboxSize:
		lwMode = -1;
		break;

    default:
	    Bug("Unknown tool type");
		}

    //
	// Check if we've changed primary tool type
	//
	switch (tool)
		{

	case toolActorNuke:
	case toolCopyObject:
	case toolCopyRte:
	case toolCutObject:
	case toolPasteObject:
		break;

	case toolSoonerLater:
		_fMouseDownSeen = fFalse;
	
	case toolSceneNuke:
	case toolDefault:
	case toolSounder:
	case toolLooper:
	case toolMatcher:
	case toolListener:
	case toolSceneChop:
	case toolSceneChopBack:
	case toolAction:
	case toolActorSelect:
    case toolPlace:
	case toolCompose:
	case toolRecordSameAction:
	case toolRotateX:
	case toolRotateY:
	case toolRotateZ:
	case toolCostumeCmid:
	case toolSquashStretch:
	case toolResize:
	case toolNormalizeRot:
	case toolNormalizeSize:
	case toolActorEasel:

		_fTextMode = fFalse;

		if (lwMode > 0)
			{
			break;
			}

		if (Pmvie()->Pscen() != pvNil)
			{
			Pmvie()->Pscen()->SelectActr(pactr);
			}
		break;

	case toolTboxMove:
	case toolTboxUpDown:
	case toolTboxLeftRight:
	case toolTboxFalling:
	case toolTboxRising:
	case toolTboxStory:
	case toolTboxCredit:
	case toolTboxFillBkgd:

		_fTextMode = fTrue;

		if (lwMode < 0)
			{
			break;
			}

LSelectTbox:
		if (Pmvie()->Pscen() != pvNil)
			{
			Pmvie()->Pscen()->SelectTbox(ptbox);
			}
	    break;

	case toolTboxPaintText:

		_fTextMode = fTrue;

		if ((lwMode < 0) && (ptbox != pvNil))
			{
			ptbox->FSetAcrText(AcrPaint());
			ptbox->Pscen()->Pmvie()->Pmcc()->PlayUISound(tool);
			break;
			}

		goto LSelectTbox;

	case toolTboxFont:
		_fTextMode = fTrue;
		if ((lwMode < 0) && (ptbox != pvNil))
			{
			ptbox->FSetOnnText(OnnTextCur());
			ptbox->Pscen()->Pmvie()->Pmcc()->PlayUISound(tool);
			break;
			}
		goto LSelectTbox;

	case toolTboxSize:
		_fTextMode = fTrue;
		if ((lwMode < 0) && (ptbox != pvNil))
			{
			ptbox->FSetDypFontText(DypFontTextCur());
			ptbox->Pscen()->Pmvie()->Pmcc()->PlayUISound(tool);
			break;
			}
		goto LSelectTbox;

	case toolTboxStyle:
		_fTextMode = fTrue;
		if ((lwMode < 0) && (ptbox != pvNil))
			{
			ptbox->FSetStyleText(GrfontStyleTextCur());
			ptbox->Pscen()->Pmvie()->Pmcc()->PlayUISound(tool);
			break;
			}
		goto LSelectTbox;

	default:
	    Bug("Unknown tool type");
		}

	_tool = tool;
}


/***************************************************************************
 *
 * Change the current loaded tag attached to the cursor.  If the previous
 * tag was a "ksidUseCrf" tag, it must be closed to release its refcount on
 * the tag's pcrf.
 *
 * Parameters:
 *	ptag - The new tag to attach to the cursor
 *
 * Returns:
 *  None.
 *
***************************************************************************/
void MVU::SetTagTool(PTAG ptag)
{
	AssertThis(0);
	AssertVarMem(ptag);

	if (_tagTool.sid != ksidInvalid)
		{
		TAGM::CloseTag(&_tagTool);
		}

#ifdef DEBUG
	// Make sure the new tag has been opened, if it's a "ksidUseCrf" tag
	if (ptag->sid == ksidUseCrf)
		{
		AssertPo(ptag->pcrf, 0);
		}
#endif

	_tagTool = *ptag;
	if (_tagTool.sid != ksidInvalid)
		TAGM::DupTag(ptag);
}



/***************************************************************************
 *
 * Draw this view.
 *
 * Parameters:
 *	pgnv - The environment to write to.
 *  prcClip - The clipping rectangle.
 *
 * Returns:
 *  None.
 *
***************************************************************************/
void MVU::Draw(PGNV pgnv, RC *prcClip)
{
	AssertThis(0);
	AssertPo(pgnv, 0);
	AssertVarMem(prcClip);

	RC rcDest;

	//
	// Clear non-rendering areas
	//
	if (prcClip->xpRight > _dxp)
		{
		rcDest = *prcClip;
		rcDest.xpLeft = _dxp;
		pgnv->FillRc(&rcDest, kacrWhite);
		}
	if (prcClip->ypBottom > _dyp)
		{
		rcDest = *prcClip;
		rcDest.ypTop = _dyp;
		pgnv->FillRc(&rcDest, kacrWhite);
		}

	//
	// Render
	//
	if (Pmvie()->Pscen() != pvNil)
		{
        Pmvie()->Pbwld()->Render();
		Pmvie()->Pbwld()->Draw(pgnv, prcClip, 0, 0);

		//
		// This draws a currently being dragged out text box frame.
		//
		if (!_rcFrame.FEmpty())
			{
			pgnv->FrameRcApt(&_rcFrame, &vaptLtGray, kacrBlack, kacrWhite);
			}
		}
	else
		{
		rcDest.Set(0, 0, _dxp, _dyp);
		pgnv->FillRc(&rcDest, kacrBlack);
		}
}


/***************************************************************************
 *
 * Warps the cursor to the center of this gob.  Also sets _xpPrev and
 * _ypPrev so that future mouse deltas are from the center.
 *
 * Parameters:
 *	None.
 *
 * Returns:
 *	None.
 *
 **************************************************************************/
void MVU::WarpCursToCenter(void)
{
	AssertThis(0);

	PT pt;

	_xpPrev = _dxp / 2;
	_ypPrev = _dyp / 2;
	_dzrPrev = rZero;
	pt.xp = _xpPrev;
	pt.yp = _ypPrev;
	MapPt(&pt, cooLocal, cooGlobal);
	vpappb->PositionCurs(pt.xp, pt.yp);
}


/***************************************************************************
 *
 * Warps the cursor to the center of the given actor.
 *
 * Parameters:
 *	None.
 *
 * Returns:
 *	None.
 *
 **************************************************************************/
void MVU::WarpCursToActor(PACTR pactr)
{
	AssertThis(0);
	AssertPo(pactr, 0);

	PT pt;

	pactr->GetCenter(&pt.xp, &pt.yp);
	MapPt(&pt, cooLocal, cooGlobal);
	vpappb->PositionCurs(pt.xp, pt.yp);
}


/***************************************************************************
 *
 * Call this function when you have the cursor hidden and you want to
 * "reset" its position after using the mouse position to adjust an actor.
 * It updates _xpPrev and _ypPrev.  Then, if the cursor has gone outside
 * the gob, it warps the cursor to the center of the gob.  This way, the
 * actor doesn't seem to hit an invisible "wall" just because the (hidden)
 * cursor has hit the edge of the screen.
 *
 * Parameters:
 *	(xp, yp): current cursor position
 *
 * Returns:
 *	None.
 *
 **************************************************************************/
void MVU::AdjustCursor(long xp, long yp)
{
	AssertThis(0);

	RC rc;

	GetRc(&rc, cooLocal);
	rc.Inset(kdpInset, kdpInset); // warp before the cursor gets close to the gob's edge
	if (rc.FPtIn(xp, yp))
		{
		_xpPrev = xp;
		_ypPrev = yp;
		_dzrPrev = rZero;
		}
	else
		{
		WarpCursToCenter();
		}
}


/***************************************************************************
 *
 * Converts from mouse coordinates to world coordinates
 *
 * Parameters:
 *	dxrMouse - BRender scalar representation of mouse X coordinate
 *	dyrMouse - BRender scalar representation of mouse Y coordinate
 *	dzrMouse - BRender scalar representation of mouse Z coordinate
 *  pdxrWld  - Place to store world X coordinate.
 *  pdyrWld  - Place to store world Y coordinate.
 *  pdzrWld  - Place to store world Z coordinate.
 *  fRecord  - Is the conversion to be scaled according to the recording
 *				scaling factor, or the non-recording scaling factor.
 *
 * Returns:
 *  None.
 *
 **************************************************************************/
void MVU::MouseToWorld(BRS dxrMouse, BRS dyrMouse, BRS dzrMouse, BRS *pdxrWld,
	BRS *pdyrWld, BRS *pdzrWld, bool fRecord)
{
	AssertThis(0);
	AssertVarMem(pdxrWld);
	AssertVarMem(pdyrWld);
	AssertVarMem(pdzrWld);

	BRS dxrScr, dyrScr, dzrScr;
	BMAT34 bmat34Cam;
	BRS rScaleMouse;

	dxrScr = BR_MAC3(dxrMouse, _rgrAxis[0][0], dyrMouse, _rgrAxis[0][1], dzrMouse, _rgrAxis[0][2]);
	dyrScr = BR_MAC3(dxrMouse, _rgrAxis[1][0], dyrMouse, _rgrAxis[1][1], dzrMouse, _rgrAxis[1][2]);
	dzrScr = BR_MAC3(dxrMouse, _rgrAxis[2][0], dyrMouse, _rgrAxis[2][1], dzrMouse, _rgrAxis[2][2]);

	rScaleMouse = fRecord ? krScaleMouseRecord : krScaleMouseNonRecord;

	//
	// apply some scaling so that 1 pixel of mouse movement is rScaleMouse world units
	//
	dxrScr = BrsMul(dxrScr, rScaleMouse);
	dyrScr = BrsMul(dyrScr, rScaleMouse);
	dzrScr = BrsMul(dzrScr, rScaleMouse);

	Pmvie()->Pscen()->Pbkgd()->GetMouseMatrix(&bmat34Cam);
	*pdxrWld = BR_MAC3(dxrScr, bmat34Cam.m[0][0], dyrScr, bmat34Cam.m[1][0], dzrScr, bmat34Cam.m[2][0]);
	*pdyrWld = BR_MAC3(dxrScr, bmat34Cam.m[0][1], dyrScr, bmat34Cam.m[1][1], dzrScr, bmat34Cam.m[2][1]);
	*pdzrWld = BR_MAC3(dxrScr, bmat34Cam.m[0][2], dyrScr, bmat34Cam.m[1][2], dzrScr, bmat34Cam.m[2][2]);

}


bool MVU::_fKbdDelayed = fFalse;
long MVU::_dtsKbdDelay;
long MVU::_dtsKbdRepeat;

/***************************************************************************
 *
 * Slows down keyboard auto-repeat as much as possible.  Saves user's
 * previous setting so it can be restored in RestoreKeyboardRepeat.
 *
 * Parameters:
 *  none
 *
 * Returns
 *  none
 *
 **************************************************************************/
void MVU::SlowKeyboardRepeat(void)
{
	if (_fKbdDelayed)
		return;
#ifdef WIN
	if (!SystemParametersInfo(SPI_GETKEYBOARDDELAY, 0, &_dtsKbdDelay, fFalse))
		{
		Bug("why could this fail?");
		return;
		}
	if (!SystemParametersInfo(SPI_SETKEYBOARDDELAY, klwMax, pvNil, fFalse))
		{
		Bug("why could this fail?");
		return;
		}
	if (!SystemParametersInfo(SPI_GETKEYBOARDSPEED, 0, &_dtsKbdRepeat, fFalse))
		{
		Bug("why could this fail?");
		return;
		}
	if (!SystemParametersInfo(SPI_SETKEYBOARDSPEED, 0, pvNil, fFalse))
		{
		Bug("why could this fail?");
		return;
		}
#endif
#ifdef MAC
	RawRtn();
#endif
	_fKbdDelayed = fTrue;
}


/***************************************************************************
 *
 * Restores the keyboard auto-repeat to the user's previous setting.
 *
 * Parameters:
 *  none
 *
 * Returns
 *  none
 *
 **************************************************************************/
void MVU::RestoreKeyboardRepeat(void)
{
	if (!_fKbdDelayed)
		return;
#ifdef WIN
	if (!SystemParametersInfo(SPI_SETKEYBOARDDELAY, _dtsKbdDelay, pvNil, fFalse))
		{
		Bug("why could this fail?");
		return;
		}
	if (!SystemParametersInfo(SPI_SETKEYBOARDSPEED, _dtsKbdRepeat, pvNil, fFalse))
		{
		Bug("why could this fail?");
		return;
		}
#endif
#ifdef MAC
	RawRtn();
#endif
	_fKbdDelayed = fFalse;
}


/***************************************************************************
 *
 * An actor has just been added, so enter "place actor" mode, where the
 * actor floats with the cursor.
 *
 * Parameters:
 *  fEntireScene flags whether the whole scene's route is to be translated on
 *  positioning, or whether only the current subroute is to be translated.
 *
 * Returns
 *  None.
 *
 **************************************************************************/
void MVU::StartPlaceActor(bool fEntireScene)
{
	AssertThis(0);
	AssertPo(Pmvie()->Pscen(), 0);

	PACTR pactr = Pmvie()->Pscen()->PactrSelected();

	AssertPo(pactr, 0);

	vpappb->HideCurs();
	WarpCursToCenter();

	_fEntireScene = fEntireScene;
	SetTool(toolPlace);
	Pmvie()->RemFromRollCall(pactr, fFalse);
	Pmvie()->Pmcc()->NewActor();
	vpcex->TrackMouse(this);

	// While tracking the mouse, don't allow the cursor out of capture window.
	// If we don't do this, then the user can click on another window in the
	// middle of the cursor tracking, (if tracking with mouse btn up). Note,
	// this call clips the cursor movement to an area on the screen, so we are
	// assuming there is no way for the capture window to move during tracking.
#ifdef WIN
	RECT rectCapture;
	GetWindowRect(HwndContainer(), &rectCapture);
	ClipCursor(&rectCapture);
#endif //WIN

	_fMouseDownSeen = fFalse;
	_tsLastSample = TsCurrent();
	SlowKeyboardRepeat();

	return;
}



/***************************************************************************
 *
 * Undoes the place tool.
 *
 * Parameters:
 *  None.
 *
 * Returns
 *  fTrue if successful, else fFalse.
 *
 **************************************************************************/
void MVU::EndPlaceActor()
{
	AssertThis(0);
	AssertPo(Pmvie()->Pscen(), 0);

	if (Tool() != toolPlace)
		{
		return;
		}

	vpappb->ShowCurs();
	WarpCursToCenter();
	SetTool(toolCompose);
	AssertDo(Pmvie()->FAddToRollCall(Pmvie()->Pscen()->PactrSelected(), pvNil), "Should never fail");
	Pmvie()->Pmcc()->ChangeTool(toolCompose);
	vpcex->EndMouseTracking();

	_fMouseDownSeen = fFalse;

	return;
}
/***************************************************************************
 *
 * Track the mouse moves and set the cursor appropriately.
 *
 * Parameters:
 *	pcmd - The command information.
 *
 * Returns:
 *  fTrue - indicating that the command was processed.
 *
***************************************************************************/
bool MVU::FCmdMouseMove(PCMD_MOUSE pcmd)
{
	AssertThis(0);
	AssertVarMem(pcmd);

	PACTR pactr;
	long ibset;
	PDOCB pdocb;

	AssertPo(Pmvie(), 0);
	if (Pmvie()->Pscen() == pvNil)
		{
		Pmvie()->Pmcc()->SetCurs(toolDefault);
		return(fTrue);
		}

	if (Pmvie()->FPlaying())
		{
		Pmvie()->Pmcc()->SetCurs(toolDefault);
		return(fTrue);
		}

	switch (Tool())
		{
	case toolTboxStory:
	case toolTboxCredit:
	case toolTboxPaintText:
	case toolTboxFillBkgd:
	case toolTboxMove:
	case toolTboxFont:
	case toolTboxSize:
	case toolTboxStyle:
		Pmvie()->Pmcc()->SetCurs(toolDefault);
		break;

	case toolPlace:
		return(fFalse);

	case toolSceneNuke:
	case toolSceneChop:
	case toolSceneChopBack:
		Pmvie()->Pmcc()->SetCurs(Tool());
		break;

	case toolSounder:
	case toolLooper:
	case toolMatcher:
		if (_tagTool.sid == ksidInvalid)
			Pmvie()->Pmcc()->SetCurs(toolDefault);
		else
			Pmvie()->Pmcc()->SetCurs(Tool());
		break;

	case toolListener:
		Pmvie()->Pmcc()->SetCurs(Tool());
		// Audition sound if over an actor
		pactr = Pmvie()->Pscen()->PactrFromPt(pcmd->xp, pcmd->yp, &ibset);
 		if (pvNil != pactr)
 			{
			if (pactr != _pactrListener)
				{
				Pmvie()->Pmsq()->StopAll();
	 			pactr->FReplayFrame(fscenSounds);	// Ignore audition error; non-fatal
				// Play outstanding sounds
				Pmvie()->Pmsq()->PlayMsq();
				}
			_fMouseOn = fFalse;
			}
		else if (!_fMouseOn)
			{
			_fMouseOn = fTrue;
			Pmvie()->Pmsq()->StopAll();
			Pmvie()->Pscen()->FReplayFrm(fscenSounds);
			// Play outstanding sounds
			Pmvie()->Pmsq()->PlayMsq();
			}

		_pactrListener = pactr;
		break;

	case toolSoonerLater:
		if (!_fTextMode)
			{
	        AssertPo(Pmvie()->Pscen(), 0);
			pactr = Pmvie()->Pscen()->PactrFromPt(pcmd->xp, pcmd->yp, &ibset);
			AssertNilOrPo(pactr, 0);
			if (pactr == pvNil)
				{
				Pmvie()->Pmcc()->SetCurs(toolDefault);
				break;
				}
			else if (_fMouseDownSeen)
				{
				Pmvie()->Pmcc()->SetCurs(toolCompose);
				}
			else
				{
				Pmvie()->Pmcc()->SetCurs(Tool());
				}
			}
		else
			{
			Pmvie()->Pmcc()->SetCurs(toolDefault);
			}
		break;

	case toolAction:
	case toolActorNuke:
	case toolActorSelect:
	case toolCompose:
	case toolRecordSameAction:
	case toolRotateX:
	case toolRotateY:
	case toolRotateZ:
	case toolCostumeCmid:
	case toolSquashStretch:
	case toolResize:
	case toolNormalizeRot:
	case toolNormalizeSize:
	case toolCopyObject:
	case toolPasteObject:
	case toolCopyRte:
	case toolCutObject:
	case toolActorEasel:

		if (!_fTextMode)
			{
	        AssertPo(Pmvie()->Pscen(), 0);
			pactr = Pmvie()->Pscen()->PactrFromPt(pcmd->xp, pcmd->yp, &ibset);
			AssertNilOrPo(pactr, 0);
			if (pactr == pvNil)
				{
				Pmvie()->Pmcc()->SetCurs(toolDefault);
				break;
				}
			else if ((Tool() == toolCompose) && (pcmd->grfcust & fcustCmd))
				{
				Pmvie()->Pmcc()->SetCurs(toolTweak);
				}
			else if ((Tool() == toolCompose) && (pcmd->grfcust & fcustShift))
				{
				Pmvie()->Pmcc()->SetCurs(toolComposeAll);
				}
			else if ((Tool() == toolPasteObject) &&
					vpclip->FGetFormat(kclsACLP, &pdocb))
				{
				if (((PACLP)pdocb)->FRouteOnly())
					Pmvie()->Pmcc()->SetCurs(toolPasteRte);
				else
					Pmvie()->Pmcc()->SetCurs(Tool());
				ReleasePpo(&pdocb);
				}
			else
				{
				Pmvie()->Pmcc()->SetCurs(Tool());
				}
			}
		else
			{
			Pmvie()->Pmcc()->SetCurs(toolDefault);
			}
		break;

	case toolDefault:
		Pmvie()->Pmcc()->SetCurs(toolDefault);
		break;

	default:
	    Bug("Unknown tool type");
		}

	return(fTrue);
}

/***************************************************************************
 *
 * Track the mouse and do the appropriate command.
 *
 * Parameters:
 *	pcmdTrack - The command information.
 *
 * Returns:
 *  fTrue - indicating that the command was processed.
 *
***************************************************************************/
bool MVU::FCmdTrackMouse(PCMD_MOUSE pcmd)
{
	AssertThis(0);
	AssertVarMem(pcmd);

	RC rc;

	if (pcmd->cid == cidMouseDown)
		{
		Assert(vpcex->PgobTracking() == pvNil, "mouse already being tracked!");
		vpcex->TrackMouse(this);
		}
	else
		{
		Assert(vpcex->PgobTracking() == this, "not tracking mouse!");
		Assert(pcmd->cid == cidTrackMouse, 0);
		}

	if ((pcmd->cid == cidMouseDown) || ((pcmd->grfcust & fcustMouse) && !_fMouseDownSeen))
		{
		_MouseDown(pcmd);
		}
	else
		{
		_MouseDrag(pcmd);
		}	

	if (!(pcmd->grfcust & fcustMouse))
		{
		_MouseUp(pcmd);
		}

	return fTrue;
}


/***************************************************************************
 *
 * Handle positioning an actor when the place tool is in effect.
 *
 * Parameters:
 *  dxrWld, dyrWld, dzrWld - the change in position in worldspace
 *
 * Returns:
 *  None.
 *
 **************************************************************************/
void MVU::_PositionActr(BRS dxrWld, BRS dyrWld, BRS dzrWld)
{
	AssertThis(0);
	Assert(Tool() == toolPlace, "Wrong tool in effect");

	PMVIE pmvie;
	PSCEN pscen;
	bool  fMoved;
	PACTR pactr = pvNil;
	ulong grfmaf = fmafOrient;

	pmvie = Pmvie();
	AssertPo(pmvie, 0);

	pscen = pmvie->Pscen();
	AssertPo(pscen, 0);

	pactr = pscen->PactrSelected();
	AssertPo(pactr, 0);

	if (_fEntireScene)
		{
		grfmaf |= fmafEntireScene;
		}
	else
		{
		grfmaf |= fmafEntireSubrte;
		}

	if (FRespectGround())
		{
		grfmaf |= fmafGround;
		}

	// FMoveRouteCore cannot fail on fmafEntireSubrte as no events are added
	if (pactr->FMoveRoute(dxrWld, dyrWld, dzrWld, &fMoved, grfmaf) && fMoved)
		{
		Pmvie()->Pbwld()->MarkDirty();
		Pmvie()->MarkViews();
		pscen->Pbkgd()->ReuseActorPlacePoint();
		}
}

/***************************************************************************
 *
 * Notify script that an actor was clicked.  Note that we sometimes call
 * this function with fDown fFalse even though the user hasn't mouseup'ed
 * yet, because of bringing up easels on mousedown.
 *
 * Parameters:
 *	pactr - the actor that was clicked
 *  fDown - fTrue if we're mousedown'ing the actor, fFalse if mouseup
 *
 * Returns:
 *  None.
 *
 **************************************************************************/
void MVU::_ActorClicked(PACTR pactr, bool fDown)
{
	AssertThis(0);
	AssertPo(pactr, 0);

	ulong grftmpl = 0;

	if (pactr->Ptmpl()->FIsTdt())
		{
		grftmpl |= ftmplTdt;
		}

	if (pactr->Ptmpl()->FIsProp())
		{
		grftmpl |= ftmplProp;
		}

	if (fDown)
		{
		vpcex->EnqueueCid(cidActorClickedDown, pvNil, pvNil, pactr->Arid(),
			pactr->Ptmpl()->Cno(), grftmpl);
		}
	else
		{
		vpcex->EnqueueCid(cidActorClicked, pvNil, pvNil, pactr->Arid(),
			pactr->Ptmpl()->Cno(), grftmpl);
		}
}

/***************************************************************************
 *
 * Handle Mousedown
 *
 * Parameters:
 *	pcmd - The mouse command
 *
 * Returns:
 *  None.
 *
 **************************************************************************/
void MVU::_MouseDown(CMD_MOUSE *pcmd)
{
	AssertThis(0);
	AssertVarMem(pcmd);

	PACTR pactr = pvNil;
	PACTR pactrDup;
	PTBOX ptbox;
	PAUND paund;
	PT pt;
	long ibset;
	PDOCB pdocb;

	SlowKeyboardRepeat();

	if (Pmvie()->FPlaying())
		{
		//
		// If we are in the middle of playing, ignore mouse down.
		//
		return;
		}

	AssertPo(Pmvie(), 0);
	if (pvNil == Pmvie()->Pscen())
		{
		return;
		}
	AssertPo(Pmvie()->Pscen(), 0);

	if (_fTextMode)
		{
		ptbox = Pmvie()->Pscen()->PtboxSelected();
		}
	else if ((Tool() != toolPlace) &&
			 (Tool() != toolSceneChop) &&
			 (Tool() != toolSceneChopBack))
		{

		//
		// Select the actor under the cursor
		//
		pactr = Pmvie()->Pscen()->PactrSelected();
		AssertNilOrPo(pactr, 0);

		if ((pactr != pvNil) && pactr->FTimeFrozen())
			{
			pactr->SetTimeFreeze(fFalse);
			}

		pactrDup = Pmvie()->Pscen()->PactrFromPt(pcmd->xp, pcmd->yp, &ibset);

        //
		// Use previously selected actor if mouse in the actor.
		// Don't change the selected actor if we're using the default tool
		//
		if (((pactr == pvNil) ||
			 !pactr->FIsInView() ||
			 !pactr->FPtIn(pcmd->xp, pcmd->yp, &ibset)) &&
			Tool() != toolDefault)
			{
			pactr = pactrDup;
			AssertNilOrPo(pactr, 0);
			}

		if (pvNil != pactr)
			{
			_ActorClicked(pactr, fTrue);
			}
		Pmvie()->Pscen()->SelectActr(pactr); 	   // okay even if pactr is pvNil
		Pmvie()->Pbwld()->MarkDirty();
		}

#ifdef DEBUG
	// Authoring hack to write out background starting pos data

	if (pvNil != pactr && ((vpappb->GrfcustCur(fFalse) & (fcustShift | fcustCmd | fcustOption)) ==
		(fcustShift | fcustCmd | fcustOption)))
		{
		BRS xr;
		BRS yr;
		BRS zr;

		pactr->Pbody()->GetPosition(&xr, &yr, &zr);
		if (!Pmvie()->Pscen()->Pbkgd()->FWritePlaceFile(xr, yr, zr))
			{
			Bug("ouch.  bkgd write failed.");
			}
		else
			{
			Warn("Wrote bkgd actor start point.");
			}
		}
#endif // DEBUG

	_xpPrev = pcmd->xp;
	_ypPrev = pcmd->yp;
	_dzrPrev = rZero;
	_grfcust = pcmd->grfcust;
	_tsLastSample = TsCurrent();

	switch(Tool())
		{

	// Sound tools get handled together:
	case toolSounder:
	case toolLooper:
	case toolMatcher:
		if (pactr == pvNil) // scene sound
			{
			bool fLoop = (Tool() == toolLooper);
			bool fQueue = FPure(_grfcust & fcustCmd);

			if ((Tool() == toolMatcher) && (ksidInvalid != _tagTool.sid))
				{
			    PushErc(ercSocBadSceneSound);				
				break;
				}
			if (ksidInvalid != _tagTool.sid)
				{
				Pmvie()->FAddBkgdSnd(&_tagTool, fLoop, fQueue, vlmNil, styNil);
				}
			}
		else // actor sound
			{
			bool fLoop = (Tool() == toolLooper);
			bool fQueue = FPure(_grfcust & fcustCmd);
			bool fActnCel = FPure(Tool() == toolMatcher);

			if (ksidInvalid != _tagTool.sid)
				{
				Pmvie()->FAddActrSnd(&_tagTool, fLoop, fQueue, fActnCel, vlmNil, styNil);
				}
			}
		break;

	case toolListener:
		// Start the listener easel
		vpcex->EndMouseTracking();
		RestoreKeyboardRepeat();
		if (pvNil != pactr)
			{
			_ActorClicked(pactr, fFalse);
			}
		Pmvie()->Pmcc()->StartListenerEasel();
		break;

	case toolActorSelect:
		Pmvie()->Pmcc()->PlayUISound(Tool());
		break;

	case toolSceneNuke:		
		if (Pmvie()->FRemScen(Pmvie()->Iscen()))
			{
			Pmvie()->Pmcc()->UpdateRollCall();
			Pmvie()->Pmcc()->SceneNuked();
			Pmvie()->InvalViewsAndScb();
			Pmvie()->Pmcc()->PlayUISound(Tool());
			}
		break;

	case toolActorNuke:
		if (pactr == pvNil)
			{
            break;
			}

		Pmvie()->FRemActr();
		Pmvie()->Pmcc()->PlayUISound(Tool());
		break;

	case toolSceneChop:
		if (Pmvie()->Pscen()->FChop())
			{
			Pmvie()->InvalViewsAndScb();
			Pmvie()->Pmcc()->PlayUISound(Tool());
			}
		break;

	case toolSceneChopBack:
		if (Pmvie()->Pscen()->FChopBack())
			{
			Pmvie()->InvalViewsAndScb();
			Pmvie()->Pmcc()->PlayUISound(Tool());
			}
		break;

	case toolCutObject:
	case toolCopyObject:
	case toolCopyRte:
		if (!_fTextMode)
			{
			FDoClip(Tool());
			}
		break;

	case toolPasteObject:
		if (!_fTextMode)
			{
			if (vpclip->FGetFormat(kclsACLP, &pdocb) &&
				((PACLP)pdocb)->FRouteOnly() &&
				(pactr != pvNil))
				{
				FDoClip(Tool());
				ReleasePpo(&pdocb);
				}
			}
		break;

	case toolTboxStory:
	case toolTboxCredit:
	case toolTboxPaintText:
	case toolTboxFillBkgd:
	case toolTboxMove:
	case toolTboxFont:
	case toolTboxSize:
	case toolTboxStyle:
		Pmvie()->Pscen()->SelectTbox(pvNil);
		break;

	case toolPlace:
		_fMouseDownSeen = fTrue;
		break;
		
	case toolSoonerLater:	
		if ((pactr != pvNil) && !_fMouseDownSeen)
			{
			pactr->SetTimeFreeze(fTrue);
			pactr->Hilite();
			_fMouseDownSeen = fTrue;
			Pmvie()->Pmcc()->PlayUISound(Tool());
			}
		else
			{
			goto LEnd;
			}
		break;

	case toolCompose:
	case toolRotateX:
	case toolRotateY:
	case toolRotateZ:
	case toolResize:
	case toolSquashStretch:
		if (pactr != pvNil)
			{

			vpappb->HideCurs();

			//
			// Create an actor undo object
			//
			paund = AUND::PaundNew();
			if ((paund == pvNil) || !pactr->FDup(&pactrDup, fTrue))
				{
                Pmvie()->ClearUndo();
			    PushErc(ercSocNotUndoable);
				}
			else
				{
				paund->SetPactr(pactrDup);
				ReleasePpo(&pactrDup);	  	
				paund->SetArid(pactr->Arid());

				//
				// Store it.  We will only add it if there is a change done
				// to the actor.
				//
				_paund = paund;
				}

			if ((Tool() != toolResize) && (Tool() != toolSquashStretch))
				{
				Pmvie()->Pmcc()->PlayUISound(Tool(), _grfcust);
				}
			else
				{
				_lwLastTime = 0;
				}

			}
		break;

	case toolNormalizeRot:
		if (pactr != pvNil)
			{
			Pmvie()->Pmcc()->PlayUISound(Tool());		
			pactr->FNormalize(fnormRotate);
			}
		break;
	case toolNormalizeSize:
		if (pactr != pvNil)
			{
			Pmvie()->Pmcc()->PlayUISound(Tool());		
			pactr->FNormalize(fnormSize);
			}
		break;

	case toolRecordSameAction:

		if (pactr != pvNil)
			{
			long anidTool = pactr->AnidCur();
			long anid = anidTool;
			long celn = 0;
			bool fFrozen;
			
			SetAnidTool(pactr->AnidCur());
			_ptmplTool = pactr->Ptmpl();

			if ((pcmd->grfcust & fcustShift) &&
				(pcmd->grfcust & fcustCmd) &&
				FRecordDefault())
				{
				fFrozen = fFalse;
				_fCyclingCels = fFalse;
				_fSetFRecordDefault = fTrue;
				SetFRecordDefault(fFalse);
				}
			else
				{
				fFrozen = FPure(pcmd->grfcust & fcustShift);
				_fCyclingCels = FPure(pcmd->grfcust & fcustCmd);
				}

			if ((pactr->Ptmpl() != _ptmplTool) &&
				!(_ptmplTool->FIsTdt() && pactr->Ptmpl()->FIsTdt()))
				{
                PushErc(ercSocActionNotApplicable);
				return;
				}

			vpappb->HideCurs();
			
			// first, call FSetAction	
			if (anidTool == ivNil)
				{
				anid = pactr->AnidCur();
				}
			if (pactr->AnidCur() == anid)
				{
				celn = pactr->CelnCur();
				}

			// note that FSetAction creates an undo object
			// NOTE:  FSetAction() must be called on each use
			// of toolRecordSameAction. (It is not redundant).
			// Otherwise, resizing can break wysiwyg, as the final
			// path point probably won't be a complete cel's distance
			// from the previous step.
			Assert(pvNil == _pactrRestore, "_pactrRestore should not require releasing");
			ReleasePpo(&_pactrRestore); 	// To be safe
			if (!pactr->FSetAction(anid, celn, fFrozen, &_pactrRestore))
				{
				break; // an Oom erc has already been pushed
				}
			SetAnidTool(ivNil);
			_tsLast = TsCurrent();
			pactr->SetTsInsert(_tsLast);			
			Pmvie()->Pmcc()->PlayUISound(Tool());
			}

		break;

	case toolAction:
		//
		// Start the action browser
		//
		vpcex->EndMouseTracking();
		RestoreKeyboardRepeat();
	
		if (pactr != pvNil)
			{
			_ActorClicked(pactr, fFalse);
			Pmvie()->Pscen()->SelectActr(pactr);
			_ptmplTool = pactr->Ptmpl();
			Pmvie()->Pmcc()->StartActionBrowser();
			}
		break;

	case toolCostumeCmid:
		if (pactr != pvNil)
			{
			TAG tag; // unused
			TrashVar(&tag);
			TrashVar(&ibset);
			Pmvie()->FCostumeActr(ibset, &tag, CmidTool(), fTrue);
			}
		break;
	case toolActorEasel:
		if (pactr != pvNil)
			{
			bool fActrChanged;

			_ActorClicked(pactr, fFalse);
			Pmvie()->Pmcc()->ActorEasel(&fActrChanged);
			if (fActrChanged)
				{
			    Pmvie()->SetDirty();
				Pmvie()->ClearUndo();
				}
			}
		break;

	case toolDefault:
		/* Do nothing */
		break;

	default:
		Bug("Tool unknown on mouse down");
		}

	if (Pmvie()->FSoundsEnabled())
		{
		Pmvie()->Pmsq()->PlayMsq();
		}
	else
		{
		Pmvie()->Pmsq()->FlushMsq();
		}
	_fMouseDownSeen = fTrue;

LEnd:
	Pmvie()->MarkViews();

}


/***************************************************************************
 *
 * Handle Mouse drag (mouse move while button down)
 *
 * Parameters:
 *  pcmd - The mouse command
 *
 * Returns:
 *  None.
 *
 **************************************************************************/
void MVU::_MouseDrag(CMD_MOUSE *pcmd)
{
	AssertThis(0);
	AssertVarMem(pcmd);

	PMVIE pmvie;
	PSCEN pscen;
	PACTR pactr = pvNil;
	BRS dxrMouse, dyrMouse, dzrMouse;
	BRS dxrWld, dyrWld, dzrWld; // amount moved from previous point in world space
	BRS zrActr, zrCam, dzrActr;
	bool fArrowKey = fFalse;
	RC rc;
	PT pt;

	if (Pmvie()->FPlaying() || Pmvie()->Pmcc()->FMinimized())
		{
		//
		// If we are in the middle of playing, or been minimized, ignore mouse dragging.
		//
		return;
		}

	pmvie = Pmvie();
	AssertPo(pmvie, 0);
	pscen = pmvie->Pscen();
	AssertNilOrPo(pscen, 0);

	if (pvNil == pscen)
		{
        return;
		}
	
	pactr = pscen->PactrSelected();

	AssertNilOrPo(pactr, 0);

	if (pactr == pvNil)
		{
		return;
		}


	dxrMouse = BrsSub(BrIntToScalar(pcmd->xp), BrIntToScalar(_xpPrev));
	dyrMouse = BrsSub(BrIntToScalar(_ypPrev), BrIntToScalar(pcmd->yp));
	dzrMouse = _dzrPrev;
#ifdef WIN
	//
	// Get the "mouse Z" by sampling the arrow keys.  If an arrow key
	// is down, the number of pixels moved is the number of seconds
	// since the keyboard was last sampled times kdwrMousePerSecond.
	//
	ulong dts = LwMax(1, TsCurrent() - _tsLastSample);
	BRS drSec = BrsDiv(BrIntToScalar(dts), BR_SCALAR(kdtsSecond));
	if (GetKeyState(VK_UP) < 0)
		{
		fArrowKey = fTrue;
		dzrMouse += BrsMul(drSec, kdwrMousePerSecond);
		}
	if (GetKeyState(VK_DOWN) < 0)
		{
		fArrowKey = fTrue;
		dzrMouse -= BrsMul(drSec, kdwrMousePerSecond);
		}
	if (fArrowKey && Tool() == toolRecordSameAction &&
		!_fCyclingCels && pactr->FIsModeRecord())
		{
		if ((BrsAbs(dxrMouse) * 4 < BrsAbs(dzrMouse)) &&
			(BrsAbs(dyrMouse) * 4 < BrsAbs(dzrMouse)))
			{
			// When the up/down arrow keys are used, infinitesimal
			// mouse movement should not be observed or determine path direction.
			dxrMouse = rZero;
			dyrMouse = rZero;
			}
		}
	_dzrPrev = dzrMouse;
	_tsLastSample = TsCurrent();
#endif
#ifdef MAC
	RawRtn();
#endif

	MouseToWorld(dxrMouse, dyrMouse, dzrMouse, &dxrWld, &dyrWld, &dzrWld, Tool() == toolRecordSameAction);

	//
	// Scale movement based on selected actor
	//
	pactr->GetXyzWorld(pvNil, pvNil, &zrActr);
	pscen->Pbkgd()->GetCameraPos(pvNil, pvNil, &zrCam);
	dzrActr = BrsAbs(BrsSub(zrActr, zrCam));
	dzrActr = BrsDiv(dzrActr, kzrMouseScalingFactor);
	if (dzrActr < rOne)
		{
		dzrActr = rOne;
		}
	dxrWld = BrsMul(dxrWld, BrsMul(dzrActr, BR_SCALAR(1.1)));
	dyrWld = BrsMul(dyrWld, BrsMul(dzrActr, BR_SCALAR(1.1)));
	dzrWld = BrsMul(dzrWld, BrsMul(dzrActr, BR_SCALAR(1.1)));


	switch(Tool())
		{
	default:
		Bug("Tool unknown on mouse move");
		break;

	case toolDefault:
	case toolActorSelect:
		break;

	case toolSceneNuke:
	case toolActorNuke:
	case toolSceneChop:
	case toolSceneChopBack:
	case toolCutObject:
	case toolCopyObject:
	case toolPasteObject:
	case toolCopyRte:
	case toolTboxPaintText:
	case toolTboxFillBkgd:
	case toolTboxMove:
	case toolTboxFont:
	case toolTboxSize:
	case toolTboxStory:
	case toolTboxCredit:
	case toolTboxStyle:
	case toolActorEasel:
	case toolSounder:
	case toolLooper:
	case toolMatcher:
		break;

	case toolPlace:
		_PositionActr(dxrWld, dyrWld, dzrWld);
		AdjustCursor(pcmd->xp, pcmd->yp);
		break;

	case toolCompose:
		{
		ulong grfmaf = fmafNil;
		bool fMoved;

		if (_fRespectGround)
			{
			grfmaf |= fmafGround;
			}

		if (_grfcust & fcustCmd)
			{
    		AdjustCursor(pcmd->xp, pcmd->yp);
	
			if (pactr->FTweakRoute(dxrWld, dyrWld, dzrWld, grfmaf))
    			{
				if (fMoved)
					{
					if ((_paund != pvNil) && !Pmvie()->FAddUndo(_paund))
						{
						PushErc(ercSocNotUndoable);
						Pmvie()->ClearUndo();
						}

					ReleasePpo(&_paund);
					}
				}

    		Pmvie()->MarkViews();
			}
		else
			{

			if (_grfcust & fcustShift)
				{
				grfmaf |= fmafEntireSubrte;
				}

    		// FMoveRoute returns fTrue if the distance moved was non-zero
			if (pactr->FMoveRoute(dxrWld, dyrWld, dzrWld, &fMoved, grfmaf))
    			{
				if (fMoved)
					{
					if ((_paund != pvNil) && !Pmvie()->FAddUndo(_paund))
						{
						PushErc(ercSocNotUndoable);
						Pmvie()->ClearUndo();
						}

					ReleasePpo(&_paund);

					AdjustCursor(pcmd->xp, pcmd->yp);
					Pmvie()->Pbwld()->MarkDirty();
					Pmvie()->MarkViews();
					}
    			}
			}
		}
   		break;

	case toolRotateX:
	case toolRotateY:
	case toolRotateZ:
		{
		BRS brs;
		BRA xa, ya, za;

		brs = BrsMul(dxrMouse + dyrMouse, -krRotateScaleFactor);

		xa = aZero;
		ya = aZero;
		za = aZero;

		switch (Tool())
			{
		case toolRotateX:
			xa = BrScalarToAngle(brs);
			break;
		case toolRotateY:
			ya = -BrScalarToAngle(brs);	
			break;
		case toolRotateZ:
			za = BrScalarToAngle(brs);
			break;
			}

		if (pmvie->FRotateActr(xa, ya, za, FPure(_grfcust & fcustCmd)))
			{
   			if ((_paund != pvNil) && !Pmvie()->FAddUndo(_paund))
   				{
   				PushErc(ercSocNotUndoable);
   				Pmvie()->ClearUndo();
   				}

   			ReleasePpo(&_paund);			
			}

		Pmvie()->Pbwld()->MarkDirty();
		Pmvie()->MarkViews();
		AdjustCursor(pcmd->xp, pcmd->yp);
		}
		break;
	
	case toolResize:
		{
		BRS brs;
		BRS brs2;

		brs = BrsMul(dxrMouse + dyrMouse, krRotateScaleFactor);
		brs2 = BrsAdd(brs, rOne);

		//
		// Play UI sound
		//
		if ((((dxrMouse + dyrMouse) < 0) && !(_lwLastTime < 0)) ||
            (((dxrMouse + dyrMouse) > 0) && !(_lwLastTime > 0)))
			{
			_lwLastTime = dxrMouse + dyrMouse;
			Pmvie()->Pmcc()->StopUISound();
			Pmvie()->Pmcc()->PlayUISound(Tool(), (dxrMouse + dyrMouse > 0) ? 0 : fcustShift);
			}

		if (pmvie->FScaleActr(brs2))
			{
   			if ((_paund != pvNil) && !Pmvie()->FAddUndo(_paund))
   				{
   				PushErc(ercSocNotUndoable);
   				Pmvie()->ClearUndo();
   				}

   			ReleasePpo(&_paund);			
			}

		Pmvie()->Pbwld()->MarkDirty();
		Pmvie()->MarkViews();
		AdjustCursor(pcmd->xp, pcmd->yp);
		}
		break;

	case toolSquashStretch:
		{
		BRS brs;
		BRS brs2;

		brs = BrsMul(-dxrMouse - dyrMouse, krRotateScaleFactor);
		brs2 = BrsAdd(brs, rOne);

		//
		// Play UI sound
		//
		if ((((-dxrMouse - dyrMouse) < 0) && !(_lwLastTime < 0)) ||
            (((-dxrMouse - dyrMouse) > 0) && !(_lwLastTime > 0)))
			{
			_lwLastTime = -dxrMouse - dyrMouse;
			Pmvie()->Pmcc()->StopUISound();
			Pmvie()->Pmcc()->PlayUISound(Tool(), (-dxrMouse - dyrMouse < 0) ? 0 : fcustShift);
			}

		if (pmvie->FSquashStretchActr(brs2))
			{
   			if ((_paund != pvNil) && !Pmvie()->FAddUndo(_paund))
   				{
   				PushErc(ercSocNotUndoable);
   				Pmvie()->ClearUndo();
   				}

   			ReleasePpo(&_paund);			
			}

		Pmvie()->Pbwld()->MarkDirty();
		Pmvie()->MarkViews();
		AdjustCursor(pcmd->xp, pcmd->yp);
		}
		break;

	case toolSoonerLater:
	case toolNormalizeRot:
	case toolNormalizeSize:
	case toolCostumeCmid:
		break;

	case toolRecordSameAction:
		{
		bool fLonger;
		bool fStep;
		ulong tsCurrent = TsCurrent();
		ulong grfmaf = 0;
		bool fFrozen = FPure((pcmd->grfcust & fcustShift) && !(pcmd->grfcust & fcustCmd));

		if ((pactr->Ptmpl() != _ptmplTool) &&
			!(_ptmplTool->FIsTdt() && pactr->Ptmpl()->FIsTdt()))
			{
			return;
			}

		// If have stopped cycling cels
		if (_fCyclingCels && !(pcmd->grfcust & fcustCmd))
			{
			_fCyclingCels = fFalse;
			_tsLast = tsCurrent;
			}

		// Start recording unless we're cycling cels
		if (!_fCyclingCels && !pactr->FIsModeRecord() &&
			pactr->FIsRecordValid(dxrWld, dyrWld, dzrWld, tsCurrent))
			{
			if (!pactr->FBeginRecord(tsCurrent, FRecordDefault(), _pactrRestore))
				break; // an Oom erc has already been pushed
			
			// If rerecording, nuke the remainder of the subroute
			if (FRecordDefault())
				pactr->DeleteFwdCore(fFalse);

			Pmvie()->Pmcc()->Recording(fTrue, FRecordDefault());

			}
	
		if (_fCyclingCels)
			{
			if (pcmd->grfcust & fcustCmd) // still cycling
				{
				if ((tsCurrent - _tsLast) < kdtsCycleCels)
					{
					break;
					}
				_tsLast = tsCurrent;
				if (!pactr->FSetActionCore(pactr->AnidCur(), pactr->CelnCur() + 1, fFrozen))
					{
					break; // an Oom erc has already been pushed
					}
				Pmvie()->MarkViews();
				}
			}
		else if (pactr->FIsModeRecord()) // just recording
			{
			if ((tsCurrent - _tsLast) < kdtsFrame)
				{
				break;
				}
			_tsLast = tsCurrent;
			if (fFrozen)
				{
				grfmaf |= fmafFreeze;
				}

            if (_fRespectGround)
				{
                grfmaf |= fmafGround;
				}

			if (!pactr->FRecordMove(dxrWld, dyrWld, dzrWld, grfmaf,	tsCurrent,
				&fLonger, &fStep, _pactrRestore))
				{
		        // Oom erc already pushed
				break;
				}
			if (fLonger)  	//If a point was added to the path
				{
				// update scroll bars
				Pmvie()->Pmcc()->UpdateScrollbars();
				if (fStep)
					AdjustCursor(pcmd->xp, pcmd->yp);
				Pmvie()->MarkViews();
				}
			}
		}
		break;
		}
}


/***************************************************************************
 *
 * Handle Mouseup
 *
 * Parameters:
 *  pcmd - The mouse command.
 *
 * Returns:
 *  None.
 *
 **************************************************************************/
void MVU::_MouseUp(CMD_MOUSE *pcmd)
{
	AssertThis(0);
	AssertVarMem(pcmd);

	PMVIE pmvie;
	PSCEN pscen;
	PACTR pactr = pvNil;
	PACTR pactrDup;
	PSUNA psuna;

	pmvie = Pmvie();
	AssertPo(pmvie, 0);

	_grfcust = fcustNil;

	if (Tool() != toolPlace || _fMouseDownSeen)
		RestoreKeyboardRepeat();

	if (_fPause)
		{

		Assert(Pmvie()->FPlaying(), "Bad Pause type");

		//
		// If we are pausing in the middle of playing, restart playing
		//
        if (!pmvie->Pclok()->FSetAlarm(0, pmvie))
			{
            CMD cmd;

			pmvie->SetFStopPlaying(fTrue);
			cmd.pcmh = pmvie;
			cmd.cid = cidAlarm;
			pmvie->FCmdAlarm(&cmd);
			}

			goto LEndTracking;
		}

    if (Pmvie()->FPlaying())
		{
		goto LEndTracking;
		}

	Pmvie()->Pmcc()->StopUISound();

	pscen = pmvie->Pscen();
	AssertNilOrPo(pscen, 0);
	if (pvNil == pscen)
		{
		goto LEndTracking;
		}

	pactr = pscen->PactrSelected();
	AssertNilOrPo(pactr, 0);
	if (pvNil != pactr && Tool() != toolPlace)
		{
		_ActorClicked(pactr, fFalse);
		}

	switch(Tool())
		{
	case toolDefault:
	case toolActorSelect:
		break;

	case toolPlace:

		if (!_fMouseDownSeen)
			{
            return;
			}

		pactr = Pmvie()->Pscen()->PactrSelected();
		AssertPo(pactr, 0);
		pactrDup = _pactrUndo;
		AssertNilOrPo(pactrDup, 0);

	    SetTool(toolCompose);
		Pmvie()->Pmcc()->ChangeTool(toolCompose);

		//
		// Now check if the actor is out of view
		//
		if (!pactr->FIsInView())
			{

			//
			// _pactrUndo is pvNil if this is a new actor, else it is
			// an actor from the roll call.
			//
            if (_pactrUndo != pvNil)
				{
				Pmvie()->Pscen()->FAddActrCore(_pactrUndo);	// Replace old actor with saved version.
				vpcex->EnqueueCid(cidActorPlacedOutOfView, pvNil, pvNil, _pactrUndo->Arid());
				ReleasePpo(&_pactrUndo);
				}
            else
				{
				pactr->AddRef();
                AssertDo(Pmvie()->FAddToRollCall(pactr, pvNil), "Should never fail");
				Pmvie()->Pscen()->RemActrCore(pactr->Arid());
				vpcex->EnqueueCid(cidActorPlacedOutOfView, pvNil, pvNil, pactr->Arid());
				ReleasePpo(&pactr);
				}

			WarpCursToCenter();
			vpappb->ShowCurs();
			break;

			}
		else if (_pactrUndo == pvNil)
			{
			//
			// _pactrUndo is pvNil if this is a new actor, else it is
			// an actor from the roll call.
			//
			AssertDo(Pmvie()->FAddToRollCall(pactr, pvNil), "Should never fail");
			}

		//
		// Now build an undo object for the placing of the actor
		//
		psuna = SUNA::PsunaNew();

		if ((psuna == pvNil) ||
			((_pactrUndo == pvNil) && !pactr->FDup(&pactrDup, fTrue)))
			{

            PushErc(ercSocNotUndoable);
			ReleasePpo(&pactrDup);
	        Pmvie()->ClearUndo();

			}
		else
			{

			pactrDup->SetArid(pactr->Arid());
			psuna->SetType(_pactrUndo == pvNil ? utAdd : utRep);
			psuna->SetActr(pactrDup);
			if ( _pactrUndo != pvNil)
				{
				pactrDup->AddRef();
				}

			if (!Pmvie()->FAddUndo(psuna))
				{
				PushErc(ercSocNotUndoable);
	        	Pmvie()->ClearUndo();
				}

            Pmvie()->Pmcc()->EnableActorTools();

			}

		ReleasePpo(&_pactrUndo);
	    ReleasePpo(&psuna);
		WarpCursToActor(pactr);
		vpappb->ShowCurs();
		vpcex->EnqueueCid(cidActorPlaced, pvNil, pvNil, pactr->Arid());
		break;

	case toolCompose:
	case toolRotateX:
	case toolRotateY:
	case toolRotateZ:
	case toolResize:
	case toolSquashStretch:
		if (pactr != pvNil)
			{
			WarpCursToActor(pactr);
			vpappb->ShowCurs();
			}
		break;

	case toolRecordSameAction:

		if (pvNil != pactr)
			{

			if ((pactr->Ptmpl() != _ptmplTool) &&
				!(_ptmplTool->FIsTdt() && pactr->Ptmpl()->FIsTdt()))
				{
				break;
				}

			pactr->FEndRecord(FRecordDefault(), _pactrRestore);	// On error, Oom already pushed
			ReleasePpo(&_pactrRestore);
			Pmvie()->InvalViewsAndScb();
			WarpCursToActor(pactr);
			vpappb->ShowCurs();

			}

		if (_fSetFRecordDefault)
			{
			_fSetFRecordDefault = fFalse;
			SetFRecordDefault(fTrue);
			}

		Pmvie()->Pmcc()->Recording(fFalse, FRecordDefault());

		break;

	case toolSoonerLater:
		if (pactr != pvNil)
			{
            Assert(pactr->FTimeFrozen(), "Something odd is going on");

			PACTR pactrDup;
			PAUND paund;

			paund = AUND::PaundNew();
    		if ((paund == pvNil) || !pactr->FDup(&pactrDup, fTrue))
    			{
    			Pmvie()->ClearUndo();
    			PushErc(ercSocNotUndoable);
    			}
    		else
    			{
    			paund->SetPactr(pactrDup);
    			ReleasePpo(&pactrDup);	  	
    			paund->SetArid(pactr->Arid());
				paund->SetSoonerLater(fTrue);
				paund->SetNfrmLast(Pmvie()->Pscen()->Nfrm());

				if (!Pmvie()->FAddUndo(paund))
					{
					Pmvie()->ClearUndo();
					PushErc(ercSocNotUndoable);
					}

    		    }

    		ReleasePpo(&paund);

        	Pmvie()->Pbwld()->MarkDirty();
        	Pmvie()->MarkViews();

			Pmvie()->Pmcc()->StartSoonerLater();
			}

		break;

	case toolNormalizeRot:
	case toolNormalizeSize:
	case toolCostumeCmid:
	case toolSceneNuke:
	case toolActorNuke:
	case toolSceneChop:
	case toolSceneChopBack:
	case toolCutObject:
	case toolCopyObject:
	case toolPasteObject:
	case toolCopyRte:
	case toolTboxPaintText:
	case toolTboxFillBkgd:
	case toolTboxMove:
	case toolTboxFont:
	case toolTboxSize:
	case toolTboxStyle:
	case toolActorEasel:
	case toolTboxStory:
	case toolTboxCredit:
	case toolSounder:
	case toolLooper:
	case toolMatcher:
		break;

	default:
		Bug("Tool unknown on mouse up");
		}
	AssertNilOrPo(_paund, 0);
	ReleasePpo(&_paund); 	// If you just did a mousedown then mouseup, we have a leftover
							// undo object that we don't want.  So nuke it.

LEndTracking:

	vpcex->EndMouseTracking();

	// Remove any cursor clipping we may have begun when mouse tracking started.
#ifdef WIN
	ClipCursor(NULL);
#endif //WIN

}


/***************************************************************************
 *
 * Handles the Cut, Copy, Paste and Clear commands, by setting the appropriate
 * tool.
 *
 * Parameters:
 *  pcmd - Pointer to the command to process.
 *
 * Returns:
 *  fTrue if it processed the command, else fFalse.
 *
 **************************************************************************/
bool MVU::FCmdClip(PCMD pcmd)
{
	AssertThis(0);
	AssertVarMem(pcmd);

	PTBOX ptbox;
	PDOCB pdocb;
	bool fOV = fFalse;
	CMD cmd;

	//
	// Check for O-V model for text in text box.
	//
	if (Pmvie()->Pscen() != pvNil)
		{
		AssertPo(Pmvie()->Pscen(), 0);
		
		if (FTextMode())
			{
			ptbox = Pmvie()->Pscen()->PtboxSelected();

			if ((ptbox != pvNil) && !ptbox->FIsVisible())
				{
				ptbox = pvNil;
				}
			}
		else
			{
			ptbox = pvNil;
			}

		AssertNilOrPo(ptbox, 0);	

		if (pcmd->cid == cidPasteTool &&
				vpclip->FGetFormat(kclsACLP, &pdocb))
			{
			fOV = !((PACLP)pdocb)->FRouteOnly();
			ReleasePpo(&pdocb);
			}
		else if (((ptbox != pvNil) && ptbox->FTextSelected()) ||
					((pcmd->cid == cidPasteTool) && vpclip->FGetFormat(kclsTCLP)))
			{
			if (ptbox != pvNil)
				{
				CMD cmd = *pcmd;

				cmd.pcmh = ptbox->PddgGet(0);
				vpcex->EnqueueCmd(&cmd);
				}
			fOV = fTrue;
			}

		}

	cmd = *pcmd;

	switch (pcmd->cid)
		{
	case cidCutTool:
		if (fOV)
			{
			cmd.cid = cidCut;
			vpcex->EnqueueCmd(&cmd);
			}
		else
			{
			SetTool(toolCutObject);
			}
		break;

	case cidCopyTool:
		if (fOV)
			{
			cmd.cid = cidCopy;
			vpcex->EnqueueCmd(&cmd);
			}
        else
			{
			SetTool(toolCopyObject);
			}

		break;

	case cidPasteTool:
		if (fOV)
			{

			if (vpclip->FGetFormat(kclsTCLP) ||	vpclip->FGetFormat(kclsACLP))
				{
				FDoClip(toolPasteObject);
				}
			else
				{
				cmd.cid = cidPaste;
				vpcex->EnqueueCmd(&cmd);
				}
			}
        else
			{
			SetTool(toolPasteObject);
			}

		break;

	case cidCopyRoute:
		SetTool(toolCopyRte);
		break;

	case cidPaste:

		FDoClip(toolPasteObject);
		break;

	case cidShiftCut:
		_grfcust = fcustShift;
	case cidCut:
        FDoClip(toolCutObject);
		_grfcust = fcustNil;
		break;

	case cidShiftCopy:
		_grfcust = fcustShift;
	case cidCopy:
        FDoClip(toolCopyObject);
		_grfcust = fcustNil;
		break;

	default:
		Bug("Unknown command");
		}
	return(fTrue);
}

/***************************************************************************
 *
 * Handles the Cut, Copy, Paste and Clear commands.
 *
 * Parameters:
 *  tool - The tool to apply.
 *
 * Returns:
 *  fTrue if it processed the command, else fFalse.
 *
 **************************************************************************/
bool MVU::FDoClip(long tool)
{
	AssertThis(0);

	PDOCB pdocb = pvNil;

	switch (tool)
		{
	case toolCutObject:
	case toolCopyObject:
	case toolCopyRte:

        //
		// copy the selection
		//
		if (!_FCopySel(&pdocb, tool == toolCopyRte))
			{
			return fTrue;
			}
		vpclip->Set(pdocb);
		ReleasePpo(&pdocb);

		if (tool == toolCutObject)
			{
			_ClearSel();
			}

		Pmvie()->Pmcc()->PlayUISound(tool);

		break;

	case toolPasteObject:
		if (!vpclip->FDocIsClip(pvNil))
			{
			PTCLP ptclp;

			if (vpclip->FGetFormat(kclsTCLP, (PDOCB *)&ptclp))
				{
    			AssertPo(ptclp, 0);

    			if (Pmvie()->Pscen() == pvNil)
    				{
					ReleasePpo(&ptclp);
    				return(fFalse);
    				}

				if(ptclp->FPaste(Pmvie()->Pscen()))
					{
					ReleasePpo(&ptclp);
				    Pmvie()->Pmcc()->EnableTboxTools();
					Pmvie()->Pmcc()->PlayUISound(tool);		
					return(fTrue);
					}

				ReleasePpo(&ptclp);
				Pmvie()->Pmcc()->PlayUISound(tool);
				return(fFalse);
    			}
			else
				{
				_FPaste(vpclip);
				Pmvie()->Pmcc()->PlayUISound(tool);	
				}
			}
		break;

	default:
		Bug("Bad Tool");
		}

	return fTrue;
}


/***************************************************************************
 *
 * Handles the Undo and Redo commands.
 *
 * Parameters:
 *  pcmd - Pointer to the command to process.
 *
 * Returns:
 *  fTrue if it processed the command, else fFalse.
 *
 **************************************************************************/
bool MVU::FCmdUndo(PCMD pcmd)
{
	AssertThis(0);
	AssertVarMem(pcmd);

	bool fRet;

	if (pcmd->cid == cidUndo)
		{
		Pmvie()->Pmcc()->PlayUISound(toolUndo);
		}
	else
		{
		Pmvie()->Pmcc()->PlayUISound(toolRedo);
		}

	fRet = MVU_PAR::FCmdUndo(pcmd);
    Pmvie()->Pmcc()->SetUndo(Pmvie()->CundbUndo() != 0 ? undoUndo :
							 Pmvie()->CundbRedo() != 0 ? undoRedo :
							 undoDisabled);

	Pmvie()->InvalViewsAndScb();
	return(fRet);
}

/***************************************************************************
 *
 * Handles the Copying whatever is currently selected.
 *
 * Don't worry about tboxes, because if a tbox is selected it will
 * get the cut/copy/paste command.
 *
 * Parameters:
 *  ppdocb - Pointer to a place to store a pointer to the resulting docb.
 *	fRteOnly - fTrue if to copy an actors route only, else fFalse.
 *
 * Returns:
 *  fTrue if it was successful, else fFalse.
 *
 **************************************************************************/
bool MVU::_FCopySel(PDOCB *ppdocb, bool fRteOnly)
{
	AssertThis(0);

	PACTR pactr;
	PACLP paclp;

	if (FTextMode())
		{
		return(fFalse);
		}

	if (Pmvie()->Pscen() == pvNil)
		{
		return(fFalse);
		}

	pactr = Pmvie()->Pscen()->PactrSelected();
	AssertNilOrPo(pactr, 0);

	if ((pactr == pvNil) || !pactr->FIsInView())
		{
        PushErc(ercSocNoActrSelected);
		return(fFalse);
		}

	paclp = ACLP::PaclpNew(pactr,
		fRteOnly,
		FPure(_grfcust & fcustShift));
	AssertNilOrPo(paclp, 0);

	*ppdocb = (PDOCB)paclp;

	return(paclp != pvNil);
}


/***************************************************************************
 *
 * Handles the deleting whatever is currently selected.
 *
 * Don't worry about tboxes, because if a tbox is selected it will
 * get the cut/copy/paste command.
 *
 * Parameters:
 *  None.
 *
 * Returns:
 *  None.
 *
 **************************************************************************/
void MVU::_ClearSel()
{
	AssertThis(0);

	PACTR pactr;
	bool fAlive;
	bool fEnableSounds;

	if (Pmvie()->Pscen() == pvNil)
		{
		return;
		}

	pactr = Pmvie()->Pscen()->PactrSelected();

	if (pactr == pvNil)
		{
        PushErc(ercSocNoActrSelected);
		return;
		}

	AssertPo(pactr, 0);

	fEnableSounds = !(FPure(Pmvie()->Pscen()->GrfScen() & fscenSounds));
	Pmvie()->Pscen()->Disable(fscenSounds);
	if (!pactr->FDelete(&fAlive, FPure(_grfcust & fcustShift))) 		
		{
		if (fEnableSounds)
			Pmvie()->Pscen()->Enable(fscenSounds);
        return;
		}
	if (fEnableSounds)
		Pmvie()->Pscen()->Enable(fscenSounds);

	if (!fAlive)
		{
		Pmvie()->Pscen()->RemActrCore(pactr->Arid());
		}
	else
		{
		//
		// According to design, if this fails, it is
		// ok to leave the actor on the stage.
		//
		pactr->FRemFromStageCore();
		}

	Pmvie()->Pscen()->MarkDirty();
	Pmvie()->InvalViews();
}


/***************************************************************************
 *
 * Handles the Pasting whatever is currently in the clipboard.
 *
 * Don't worry about tboxes, because if a tbox is selected it will
 * get the cut/copy/paste command.
 *
 * Parameters:
 *  pdocb - The pointer to the resulting docb.
 *
 * Returns:
 *  fTrue if it was successful, else fFalse.
 *
 **************************************************************************/
bool MVU::_FPaste(PCLIP pclip)
{
	AssertThis(0);
	AssertPo(pclip, 0);

	PACLP paclp;
	PTCLP ptclp;
	PACTR pactr;
	bool fRet;

	if (pclip->FGetFormat(kclsACLP, (PDOCB *)&paclp))
		{
		AssertPo(paclp, 0);

		if (Pmvie()->Pscen() == pvNil)
			{
			PushErc(ercSocNoScene);
			return(fFalse);
			}

		if (paclp->FRouteOnly() && FTextMode())
			{
			PushErc(ercSocCannotPasteThatHere);
			ReleasePpo(&paclp);
			return(fTrue);
			}

		pactr = Pmvie()->Pscen()->PactrSelected();
		AssertNilOrPo(pactr, 0);

		if (paclp->FRouteOnly() && ((pactr == pvNil) || !pactr->FIsInView()))
			{
			PushErc(ercSocNoActrSelected);
			ReleasePpo(&paclp);
			return(fTrue);
			}

		fRet = paclp->FPaste(Pmvie());
		ReleasePpo(&paclp);
		return fRet;
		}

	if (pclip->FGetFormat(kclsTCLP, (PDOCB *)&ptclp))
		{
		AssertPo(ptclp, 0);

		if (Pmvie()->Pscen() == pvNil)
			{
			PushErc(ercSocNoScene);
			return(fFalse);
			}

		fRet = ptclp->FPaste(Pmvie()->Pscen());
		ReleasePpo(&ptclp);
		return fRet;
		}

	PushErc(ercSocCannotPasteThatHere);
	return(fFalse);
}



/***************************************************************************
 *
 * Handle a close command.
 *
 * Parameters:
 *	fAssumeYes - Should the dialog assume yes.
 *
 * Returns:
 * 	fTrue if the client should close this document.
 *
 **************************************************************************/
bool MVU::FCloseDoc(bool fAssumeYes, bool fSaveDDG)
{
	AssertThis(0);
	bool fRet;
	FNI fni;

	//
	// FQueryClose calls FAutosave depending on the result of the query.
	// FAutosave needs to know whether the doc is closing as unused user
	// sounds are to be deleted from the movie only upon close.
	//
	Pmvie()->SetDocClosing(fTrue);
	// If not dirty, flush snds on close without user query
	// Irrelevant if there are no user sounds in the movie or if
	// the file is read-only (can't save to the original file)
	if (!Pmvie()->FDirty() && Pmvie()->FUnusedSndsUser() &&
			!Pmvie()->FReadOnly() && Pmvie()->FGetFni(&fni))
		{
		vpappb->BeginLongOp();
		fRet = _pdocb->FSave();   	// Flush sounds
		goto LSaved;
		}

	if (Pmvie()->Cscen() > 0)
		{
		fRet = _pdocb->FQueryClose(fAssumeYes ? fdocAssumeYes : fdocNil);
		}
	else
		{
		fRet = fTrue;
		}
	vpappb->BeginLongOp();

LSaved:
	Pmvie()->SetDocClosing(fFalse);
	if (fRet && !fSaveDDG)
		{
		// Beware: the following line destroys the this pointer!
		_pdocb->CloseAllDdg();
		}
	vpappb->EndLongOp();
	return fRet;
}


		
/***************************************************************************
 *
 * Handle a save, save as or save a copy command.
 *
 * Parameters:
 *	pcmd - The command to process
 *
 * Returns:
 *  fTrue.
 *
 **************************************************************************/
bool MVU::FCmdSave(PCMD pcmd)
{
	if (Pmvie()->Cscen() < 1)
		{
		PushErc(ercSocSaveFailure);
		}
	else
		{
		_pdocb->FSave(pcmd->cid);
		}

	return fTrue;
}

/***************************************************************************
 *
 * Note that we have had an idle loop.
 *
 * Parameters:
 *	pcmd - Pointer to the command to process.
 *
 * Returns:
 *  fFalse.
 *
***************************************************************************/
bool MVU::FCmdIdle(PCMD pcmd)
{
	AssertThis(0);
	AssertVarMem(pcmd);

	Pmvie()->SetFIdleSeen(fTrue);
	return(fFalse);
}


/***************************************************************************
 *
 * Note that the mouse is no longer on the view.
 *
 * Parameters:
 *	pcmd - Pointer to the command to process.
 *
 * Returns:
 *  fFalse.
 *
***************************************************************************/
bool MVU::FCmdRollOff(PCMD pcmd)
{
	AssertThis(0);
	AssertVarMem(pcmd);

	if (_fMouseOn && (Tool() == toolListener))
		{
		Pmvie()->Pmsq()->StopAll();
		}

	_fMouseOn = fFalse;

	return(fFalse);
}


#ifdef DEBUG
/***************************************************************************
 *
 * Assert the validity of the MVU.
 *
 * Parameters:
 *  grf - Bit field of options
 *
 * Returns:
 *  None.
 *
 **************************************************************************/
void MVU::AssertValid(ulong grf)
{
	MVU_PAR::AssertValid(fobjAllocated);
}

/***************************************************************************
 *
 * Mark memory used by the MVU
 *
 * Parameters:
 *  None.
 *
 * Returns:
 *  None.
 *
 **************************************************************************/
void MVU::MarkMem(void)
{
	AssertThis(0);
	MVU_PAR::MarkMem();
	MarkMemObj(Pmvie());
}
#endif // DEBUG


//
//
//
// UNDO STUFF
//
//
//

/****************************************************
 *
 * Public constructor for movie undo objects for scene
 * related commands.
 *
 * Parameters:
 *	None.
 *
 * Returns:
 *  pvNil if failure, else a pointer to the movie undo.
 *
 ****************************************************/
PMUNS MUNS::PmunsNew()
{
	PMUNS pmuns;
	pmuns = NewObj MUNS();
	return(pmuns);
}

/****************************************************
 *
 * Destructor for movies undo objects
 *
 ****************************************************/
MUNS::~MUNS(void)
{
	AssertBaseThis(0);
	ReleasePpo(&_pscen);
}


/****************************************************
 *
 * Does a command stored in an undo object.
 *
 * Parameters:
 *	pdocb - The owning document.
 *
 * Returns:
 *  fTrue if successful, else fFalse.
 *
 ****************************************************/
bool MUNS::FDo(PDOCB pdocb)
{
	AssertThis(0);

	TAG tagOld;

	switch (_munst)
		{
	case munstInsScen:
		if (!_pmvie->FNewScenInsCore(_iscen))
			{
			goto LFail;
			}
		if (!_pmvie->Pscen()->FSetBkgdCore(&_tag, &tagOld))
			{
            _pmvie->FRemScenCore(_iscen);
			goto LFail;
			}

        _pmvie->Pmcc()->SceneUnnuked();
		break;

	case munstRemScen:
		if (!_pmvie->FRemScenCore(_iscen))
			{
			goto LFail;
			}
        _pmvie->Pmcc()->SceneNuked();
		break;

    default:
		Bug("Unknown munst");
		goto LFail;
		}

	_pmvie->Pmsq()->FlushMsq();
	return(fTrue);

LFail:
	_pmvie->Pmsq()->FlushMsq();
	_pmvie->ClearUndo();  //  After this, _pmvie is invalid
	return (fFalse);
}

/****************************************************
 *
 * Undoes a command stored in an undo object.
 *
 * Parameters:
 *	pdocb - The owning document.
 *
 * Returns:
 *  fTrue if successful, else fFalse.
 *
 ****************************************************/
bool MUNS::FUndo(PDOCB pdocb)
{
	AssertThis(0);

	switch (_munst)
		{
	case munstInsScen:
		if (!_pmvie->FRemScenCore(_iscen))
			{
			goto LFail;
			}
        _pmvie->Pmcc()->SceneNuked();
		break;

	case munstRemScen:
		if (!_pmvie->FInsScenCore(_iscen, _pscen))
			{
			goto LFail;
			}
        _pmvie->Pmcc()->SceneUnnuked();

		break;

    default:
		Bug("Unknown munst");
		goto LFail;
		}

	_pmvie->Pmsq()->FlushMsq();
	return(fTrue);

LFail:
	_pmvie->Pmsq()->FlushMsq();
	_pmvie->ClearUndo();  //  After this, _pmvie is invalid
	return (fFalse);
}

#ifdef DEBUG
/****************************************************
 * Mark memory used by the MUNS
 *
 * Parameters:
 * 	None.
 *
 * Returns:
 *  None.
 *
 ****************************************************/
void MUNS::MarkMem(void)
{
	AssertThis(0);
	MUNS_PAR::MarkMem();
	MarkMemObj(_pscen);
}

/***************************************************************************
 *
 * Assert the validity of the MUNS.
 *
 * Parameters:
 *  grf - Bit field of options
 *
 * Returns:
 *  None.
 *
 **************************************************************************/
void MUNS::AssertValid(ulong grf)
{
	AssertNilOrPo(_pscen, 0);
}
#endif //DEBUG


#ifdef DEBUG
/***************************************************************************
 *
 * Assert the validity of the MUNB.
 *
 * Parameters:
 *  grf - Bit field of options
 *
 * Returns:
 *  None.
 *
 **************************************************************************/
void MUNB::AssertValid(ulong grf)
{
	MUNB_PAR::AssertValid(fobjAllocated);
	AssertPo(_pmvie, 0);
}
#endif //DEBUG
